TombBasedTreasury

Created Diff never expires
254 removals
576 lines
271 additions
597 lines
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT


pragma solidity 0.6.12;
pragma solidity ^0.8.0;


import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";


import "./lib/Babylonian.sol";
import "./lib/Babylonian.sol";
import "./owner/Operator.sol";
import "./owner/Operator.sol";
import "./utils/ContractGuard.sol";
import "./utils/ContractGuard.sol";
import "./interfaces/IBasisAsset.sol";
import "./interfaces/IBasisAsset.sol";
import "./interfaces/IOracle.sol";
import "./interfaces/IOracle.sol";
import "./interfaces/IMasonry.sol";
import "./interfaces/IAcropolis.sol";


/*
/*
______ __ _______
__________ .___ ___________.__
/_ __/___ ____ ___ / /_ / ____(_)___ ____ _____ ________
\______ \_____ ______ ____ __| _/ \_ _____/|__| ____ _____ ____ ____ ____
/ / / __ \/ __ `__ \/ __ \ / /_ / / __ \/ __ `/ __ \/ ___/ _ \
| | _/\__ \ / ___/_/ __ \ / __ | | __) | | / \ \__ \ / \ _/ ___\_/ __ \
/ / / /_/ / / / / / / /_/ / / __/ / / / / / /_/ / / / / /__/ __/
| | \ / __ \_ \___ \ \ ___/ / /_/ | | \ | || | \ / __ \_| | \\ \___\ ___/
/_/ \____/_/ /_/ /_/_.___/ /_/ /_/_/ /_/\__,_/_/ /_/\___/\___/
|______ /(____ //____ > \___ >\____ | \___ / |__||___| /(____ /|___| / \___ >\___ >

\/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/
http://tomb.finance
*/
*/
contract Treasury is ContractGuard {
contract Treasury is ContractGuard {
using SafeERC20 for IERC20;
using SafeERC20 for IERC20;
using Address for address;
using Address for address;
using SafeMath for uint256;
using SafeMath for uint256;


/* ========= CONSTANT VARIABLES ======== */
/* ========= CONSTANT VARIABLES ======== */


uint256 public constant PERIOD = 6 hours;
uint256 public constant PERIOD = 6 hours;


/* ========== STATE VARIABLES ========== */
/* ========== STATE VARIABLES ========== */


// governance
// governance
address public operator;
address public operator;


// flags
// flags
bool public initialized = false;
bool public initialized = false;


// epoch
// epoch
uint256 public startTime;
uint256 public startTime;
uint256 public epoch = 0;
uint256 public epoch = 0;
uint256 public epochSupplyContractionLeft = 0;
uint256 public epochSupplyContractionLeft = 0;


// exclusions from total supply
//=================================================================// exclusions from total supply
address[] public excludedFromTotalSupply = [
address[] public excludedFromTotalSupply = [
address(0x9A896d3c54D7e45B558BD5fFf26bF1E8C031F93b), // TombGenesisPool
address(0x9Ec66B9409d4cD8D4a4C90950Ff0fd26bB39ad84) // BasedGenesisPool
address(0xa7b9123f4b15fE0fF01F469ff5Eab2b41296dC0E), // new TombRewardPool
address(0xA7B16703470055881e7EE093e9b0bF537f29CD4d) // old TombRewardPool
];
];


// core components
// core components
address public tomb;
address public based;
address public tbond;
address public bbond;
address public tshare;
address public bshare;


address public masonry;
address public acropolis;
address public tombOracle;
address public basedOracle;


// price
// price
uint256 public tombPriceOne;
uint256 public basedPriceOne;
uint256 public tombPriceCeiling;
uint256 public basedPriceCeiling;


uint256 public seigniorageSaved;
uint256 public seigniorageSaved;


uint256[] public supplyTiers;
uint256[] public supplyTiers;
uint256[] public maxExpansionTiers;
uint256[] public maxExpansionTiers;


uint256 public maxSupplyExpansionPercent;
uint256 public maxSupplyExpansionPercent;
uint256 public bondDepletionFloorPercent;
uint256 public bondDepletionFloorPercent;
uint256 public seigniorageExpansionFloorPercent;
uint256 public seigniorageExpansionFloorPercent;
uint256 public maxSupplyContractionPercent;
uint256 public maxSupplyContractionPercent;
uint256 public maxDebtRatioPercent;
uint256 public maxDebtRatioPercent;


// 28 first epochs (1 week) with 4.5% expansion regardless of TOMB price
// 14 first epochs (0.5 week) with 4.5% expansion regardless of BASED price
uint256 public bootstrapEpochs;
uint256 public bootstrapEpochs;
uint256 public bootstrapSupplyExpansionPercent;
uint256 public bootstrapSupplyExpansionPercent;


/* =================== Added variables =================== */
/* =================== Added variables =================== */
uint256 public previousEpochTombPrice;
uint256 public previousEpochBasedPrice;
uint256 public maxDiscountRate; // when purchasing bond
uint256 public maxDiscountRate; // when purchasing bond
uint256 public maxPremiumRate; // when redeeming bond
uint256 public maxPremiumRate; // when redeeming bond
uint256 public discountPercent;
uint256 public discountPercent;
uint256 public premiumThreshold;
uint256 public premiumThreshold;
uint256 public premiumPercent;
uint256 public premiumPercent;
uint256 public mintingFactorForPayingDebt; // print extra TOMB during debt phase
uint256 public mintingFactorForPayingDebt; // print extra BASED during debt phase


address public daoFund;
address public daoFund;
uint256 public daoFundSharedPercent;
uint256 public daoFundSharedPercent;


//=================================================//

address public devFund;
address public devFund;
uint256 public devFundSharedPercent;
uint256 public devFundSharedPercent;
address public teamFund;
uint256 public teamFundSharedPercent;


/* =================== Events =================== */
/* =================== Events =================== */


event Initialized(address indexed executor, uint256 at);
event Initialized(address indexed executor, uint256 at);
event BurnedBonds(address indexed from, uint256 bondAmount);
event BurnedBonds(address indexed from, uint256 bondAmount);
event RedeemedBonds(address indexed from, uint256 tombAmount, uint256 bondAmount);
event RedeemedBonds(address indexed from, uint256 basedAmount, uint256 bondAmount);
event BoughtBonds(address indexed from, uint256 tombAmount, uint256 bondAmount);
event BoughtBonds(address indexed from, uint256 basedAmount, uint256 bondAmount);
event TreasuryFunded(uint256 timestamp, uint256 seigniorage);
event TreasuryFunded(uint256 timestamp, uint256 seigniorage);
event MasonryFunded(uint256 timestamp, uint256 seigniorage);
event AcropolisFunded(uint256 timestamp, uint256 seigniorage);
event DaoFundFunded(uint256 timestamp, uint256 seigniorage);
event DaoFundFunded(uint256 timestamp, uint256 seigniorage);
event DevFundFunded(uint256 timestamp, uint256 seigniorage);
event DevFundFunded(uint256 timestamp, uint256 seigniorage);
event TeamFundFunded(uint256 timestamp, uint256 seigniorage);


/* =================== Modifier =================== */
/* =================== Modifier =================== */


modifier onlyOperator() {
modifier onlyOperator() {
require(operator == msg.sender, "Treasury: caller is not the operator");
require(operator == msg.sender, "Treasury: caller is not the operator");
_;
_;
}
}


modifier checkCondition {
modifier checkCondition {
require(now >= startTime, "Treasury: not started yet");
require(block.timestamp >= startTime, "Treasury: not started yet");


_;
_;
}
}


modifier checkEpoch {
modifier checkEpoch {
require(now >= nextEpochPoint(), "Treasury: not opened yet");
require(block.timestamp >= nextEpochPoint(), "Treasury: not opened yet");


_;
_;


epoch = epoch.add(1);
epoch = epoch.add(1);
epochSupplyContractionLeft = (getTombPrice() > tombPriceCeiling) ? 0 : getTombCirculatingSupply().mul(maxSupplyContractionPercent).div(10000);
epochSupplyContractionLeft = (getBasedPrice() > basedPriceCeiling) ? 0 : getBasedCirculatingSupply().mul(maxSupplyContractionPercent).div(10000);
}
}


modifier checkOperator {
modifier checkOperator {
require(
require(
IBasisAsset(tomb).operator() == address(this) &&
IBasisAsset(based).operator() == address(this) &&
IBasisAsset(tbond).operator() == address(this) &&
IBasisAsset(bbond).operator() == address(this) &&
IBasisAsset(tshare).operator() == address(this) &&
IBasisAsset(bshare).operator() == address(this) &&
Operator(masonry).operator() == address(this),
Operator(acropolis).operator() == address(this),
"Treasury: need more permission"
"Treasury: need more permission"
);
);


_;
_;
}
}


modifier notInitialized {
modifier notInitialized {
require(!initialized, "Treasury: already initialized");
require(!initialized, "Treasury: already initialized");


_;
_;
}
}


/* ========== VIEW FUNCTIONS ========== */
/* ========== VIEW FUNCTIONS ========== */


function isInitialized() public view returns (bool) {
function isInitialized() public view returns (bool) {
return initialized;
return initialized;
}
}


// epoch
// epoch
function nextEpochPoint() public view returns (uint256) {
function nextEpochPoint() public view returns (uint256) {
return startTime.add(epoch.mul(PERIOD));
return startTime.add(epoch.mul(PERIOD));
}
}


// oracle
// oracle
function getTombPrice() public view returns (uint256 tombPrice) {
function getBasedPrice() public view returns (uint256 basedPrice) {
try IOracle(tombOracle).consult(tomb, 1e18) returns (uint144 price) {
try IOracle(basedOracle).consult(based, 1e18) returns (uint144 price) {
return uint256(price);
return uint256(price);
} catch {
} catch {
revert("Treasury: failed to consult TOMB price from the oracle");
revert("Treasury: failed to consult BASED price from the oracle");
}
}
}
}


function getTombUpdatedPrice() public view returns (uint256 _tombPrice) {
function getBasedUpdatedPrice() public view returns (uint256 _basedPrice) {
try IOracle(tombOracle).twap(tomb, 1e18) returns (uint144 price) {
try IOracle(basedOracle).twap(based, 1e18) returns (uint144 price) {
return uint256(price);
return uint256(price);
} catch {
} catch {
revert("Treasury: failed to consult TOMB price from the oracle");
revert("Treasury: failed to consult BASED price from the oracle");
}
}
}
}


// budget
// budget
function getReserve() public view returns (uint256) {
function getReserve() public view returns (uint256) {
return seigniorageSaved;
return seigniorageSaved;
}
}


function getBurnableTombLeft() public view returns (uint256 _burnableTombLeft) {
function getBurnableBasedLeft() public view returns (uint256 _burnableBasedLeft) {
uint256 _tombPrice = getTombPrice();
uint256 _basedPrice = getBasedPrice();
if (_tombPrice <= tombPriceOne) {
if (_basedPrice <= basedPriceOne) {
uint256 _tombSupply = getTombCirculatingSupply();
uint256 _basedSupply = getBasedCirculatingSupply();
uint256 _bondMaxSupply = _tombSupply.mul(maxDebtRatioPercent).div(10000);
uint256 _bondMaxSupply = _basedSupply.mul(maxDebtRatioPercent).div(10000);
uint256 _bondSupply = IERC20(tbond).totalSupply();
uint256 _bondSupply = IERC20(bbond).totalSupply();
if (_bondMaxSupply > _bondSupply) {
if (_bondMaxSupply > _bondSupply) {
uint256 _maxMintableBond = _bondMaxSupply.sub(_bondSupply);
uint256 _maxMintableBond = _bondMaxSupply.sub(_bondSupply);
uint256 _maxBurnableTomb = _maxMintableBond.mul(_tombPrice).div(1e18);
uint256 _maxBurnableBased = _maxMintableBond.mul(_basedPrice).div(1e18);
_burnableTombLeft = Math.min(epochSupplyContractionLeft, _maxBurnableTomb);
_burnableBasedLeft = Math.min(epochSupplyContractionLeft, _maxBurnableBased);
}
}
}
}
}
}


function getRedeemableBonds() public view returns (uint256 _redeemableBonds) {
function getRedeemableBonds() public view returns (uint256 _redeemableBonds) {
uint256 _tombPrice = getTombPrice();
uint256 _basedPrice = getBasedPrice();
if (_tombPrice > tombPriceCeiling) {
if (_basedPrice > basedPriceCeiling) {
uint256 _totalTomb = IERC20(tomb).balanceOf(address(this));
uint256 _totalBased = IERC20(based).balanceOf(address(this));
uint256 _rate = getBondPremiumRate();
uint256 _rate = getBondPremiumRate();
if (_rate > 0) {
if (_rate > 0) {
_redeemableBonds = _totalTomb.mul(1e18).div(_rate);
_redeemableBonds = _totalBased.mul(1e18).div(_rate);
}
}
}
}
}
}


function getBondDiscountRate() public view returns (uint256 _rate) {
function getBondDiscountRate() public view returns (uint256 _rate) {
uint256 _tombPrice = getTombPrice();
uint256 _basedPrice = getBasedPrice();
if (_tombPrice <= tombPriceOne) {
if (_basedPrice <= basedPriceOne) {
if (discountPercent == 0) {
if (discountPercent == 0) {
// no discount
// no discount
_rate = tombPriceOne;
_rate = basedPriceOne;
} else {
} else {
uint256 _bondAmount = tombPriceOne.mul(1e18).div(_tombPrice); // to burn 1 TOMB
uint256 _bondAmount = basedPriceOne.mul(1e18).div(_basedPrice); // to burn 1 BASED
uint256 _discountAmount = _bondAmount.sub(tombPriceOne).mul(discountPercent).div(10000);
uint256 _discountAmount = _bondAmount.sub(basedPriceOne).mul(discountPercent).div(10000);
_rate = tombPriceOne.add(_discountAmount);
_rate = basedPriceOne.add(_discountAmount);
if (maxDiscountRate > 0 && _rate > maxDiscountRate) {
if (maxDiscountRate > 0 && _rate > maxDiscountRate) {
_rate = maxDiscountRate;
_rate = maxDiscountRate;
}
}
}
}
}
}
}
}


function getBondPremiumRate() public view returns (uint256 _rate) {
function getBondPremiumRate() public view returns (uint256 _rate) {
uint256 _tombPrice = getTombPrice();
uint256 _basedPrice = getBasedPrice();
if (_tombPrice > tombPriceCeiling) {
if (_basedPrice > basedPriceCeiling) {
uint256 _tombPricePremiumThreshold = tombPriceOne.mul(premiumThreshold).div(100);
uint256 _basedPricePremiumThreshold = basedPriceOne.mul(premiumThreshold).div(100);
if (_tombPrice >= _tombPricePremiumThreshold) {
if (_basedPrice >= _basedPricePremiumThreshold) {
//Price > 1.10
//Price > 1.10
uint256 _premiumAmount = _tombPrice.sub(tombPriceOne).mul(premiumPercent).div(10000);
uint256 _premiumAmount = _basedPrice.sub(basedPriceOne).mul(premiumPercent).div(10000);
_rate = tombPriceOne.add(_premiumAmount);
_rate = basedPriceOne.add(_premiumAmount);
if (maxPremiumRate > 0 && _rate > maxPremiumRate) {
if (maxPremiumRate > 0 && _rate > maxPremiumRate) {
_rate = maxPremiumRate;
_rate = maxPremiumRate;
}
}
} else {
} else {
// no premium bonus
// no premium bonus
_rate = tombPriceOne;
_rate = basedPriceOne;
}
}
}
}
}
}


/* ========== GOVERNANCE ========== */
/* ========== GOVERNANCE ========== */


function initialize(
function initialize(
address _tomb,
address _based,
address _tbond,
address _bbond,
address _tshare,
address _bshare,
address _tombOracle,
address _basedOracle,
address _masonry,
address _acropolis,
uint256 _startTime
uint256 _startTime
) public notInitialized {
) public notInitialized {
tomb = _tomb;
based = _based;
tbond = _tbond;
bbond = _bbond;
tshare = _tshare;
bshare = _bshare;
tombOracle = _tombOracle;
basedOracle = _basedOracle;
masonry = _masonry;
acropolis = _acropolis;
startTime = _startTime;
startTime = _startTime;


tombPriceOne = 10**18;
basedPriceOne = 10**18;
tombPriceCeiling = tombPriceOne.mul(101).div(100);
basedPriceCeiling = basedPriceOne.mul(101).div(100);


// Dynamic max expansion percent
// Dynamic max expansion percent
supplyTiers = [0 ether, 500000 ether, 1000000 ether, 1500000 ether, 2000000 ether, 5000000 ether, 10000000 ether, 20000000 ether, 50000000 ether];
supplyTiers = [0 ether, 206000 ether, 386000 ether, 530000 ether, 1300000 ether, 5000000 ether, 10000000 ether];
maxExpansionTiers = [450, 400, 350, 300, 250, 200, 150, 125, 100];
maxExpansionTiers = [600, 500, 450, 400, 200, 100, 50];


maxSupplyExpansionPercent = 400; // Upto 4.0% supply for expansion
maxSupplyExpansionPercent = 600; // Upto 4.0% supply for expansion


bondDepletionFloorPercent = 10000; // 100% of Bond supply for depletion floor
bondDepletionFloorPercent = 10000; // 100% of Bond supply for depletion floor
seigniorageExpansionFloorPercent = 3500; // At least 35% of expansion reserved for masonry
seigniorageExpansionFloorPercent = 3500; // At least 35% of expansion reserved for acropolis
maxSupplyContractionPercent = 300; // Upto 3.0% supply for contraction (to burn TOMB and mint tBOND)
maxSupplyContractionPercent = 300; // Upto 3.0% supply for contraction (to burn BASED and mint bBOND)
maxDebtRatioPercent = 3500; // Upto 35% supply of tBOND to purchase
maxDebtRatioPercent = 3500; // Upto 35% supply of bBOND to purchase


premiumThreshold = 110;
premiumThreshold = 110;
premiumPercent = 7000;
premiumPercent = 7000;


// First 28 epochs with 4.5% expansion
// First 28 epochs with 4.5% expansion
bootstrapEpochs = 28;
bootstrapEpochs = 14;
bootstrapSupplyExpansionPercent = 450;
bootstrapSupplyExpansionPercent = 600;


// set seigniorageSaved to it's balance
// set seigniorageSaved to it's balance
seigniorageSaved = IERC20(tomb).balanceOf(address(this));
seigniorageSaved = IERC20(based).balanceOf(address(this));


initialized = true;
initialized = true;
operator = msg.sender;
operator = msg.sender;
emit Initialized(msg.sender, block.number);
emit Initialized(msg.sender, block.number);
}
}


function setOperator(address _operator) external onlyOperator {
function setOperator(address _operator) external onlyOperator {
operator = _operator;
operator = _operator;
}
}


function setMasonry(address _masonry) external onlyOperator {
function setAcropolis(address _acropolis) external onlyOperator {
masonry = _masonry;
acropolis = _acropolis;
}
}


function setTombOracle(address _tombOracle) external onlyOperator {
function setBasedOracle(address _basedOracle) external onlyOperator {
tombOracle = _tombOracle;
basedOracle = _basedOracle;
}
}


function setTombPriceCeiling(uint256 _tombPriceCeiling) external onlyOperator {
function setBasedPriceCeiling(uint256 _basedPriceCeiling) external onlyOperator {
require(_tombPriceCeiling >= tombPriceOne && _tombPriceCeiling <= tombPriceOne.mul(120).div(100), "out of range"); // [$1.0, $1.2]
require(_basedPriceCeiling >= basedPriceOne && _basedPriceCeiling <= basedPriceOne.mul(120).div(100), "out of range"); // [$1.0, $1.2]
tombPriceCeiling = _tombPriceCeiling;
basedPriceCeiling = _basedPriceCeiling;
}
}


function setMaxSupplyExpansionPercents(uint256 _maxSupplyExpansionPercent) external onlyOperator {
function setMaxSupplyExpansionPercents(uint256 _maxSupplyExpansionPercent) external onlyOperator {
require(_maxSupplyExpansionPercent >= 10 && _maxSupplyExpansionPercent <= 1000, "_maxSupplyExpansionPercent: out of range"); // [0.1%, 10%]
require(_maxSupplyExpansionPercent >= 10 && _maxSupplyExpansionPercent <= 1000, "_maxSupplyExpansionPercent: out of range"); // [0.1%, 10%]
maxSupplyExpansionPercent = _maxSupplyExpansionPercent;
maxSupplyExpansionPercent = _maxSupplyExpansionPercent;
}
}

// =================== ALTER THE NUMBERS IN LOGIC!!!! =================== //
function setSupplyTiersEntry(uint8 _index, uint256 _value) external onlyOperator returns (bool) {
function setSupplyTiersEntry(uint8 _index, uint256 _value) external onlyOperator returns (bool) {
require(_index >= 0, "Index has to be higher than 0");
require(_index >= 0, "Index has to be higher than 0");
require(_index < 9, "Index has to be lower than count of tiers");
require(_index <7, "Index has to be lower than count of tiers");
if (_index > 0) {
if (_index > 0) {
require(_value > supplyTiers[_index - 1]);
require(_value > supplyTiers[_index - 1]);
}
}
if (_index < 8) {
if (_index < 6) {
require(_value < supplyTiers[_index + 1]);
require(_value < supplyTiers[_index + 1]);
}
}
supplyTiers[_index] = _value;
supplyTiers[_index] = _value;
return true;
return true;
}
}


function setMaxExpansionTiersEntry(uint8 _index, uint256 _value) external onlyOperator returns (bool) {
function setMaxExpansionTiersEntry(uint8 _index, uint256 _value) external onlyOperator returns (bool) {
require(_index >= 0, "Index has to be higher than 0");
require(_index >= 0, "Index has to be higher than 0");
require(_index < 9, "Index has to be lower than count of tiers");
require(_index < 7, "Index has to be lower than count of tiers");
require(_value >= 10 && _value <= 1000, "_value: out of range"); // [0.1%, 10%]
require(_value >= 10 && _value <= 1000, "_value: out of range"); // [0.1%, 10%]
maxExpansionTiers[_index] = _value;
maxExpansionTiers[_index] = _value;
return true;
return true;
}
}


function setBondDepletionFloorPercent(uint256 _bondDepletionFloorPercent) external onlyOperator {
function setBondDepletionFloorPercent(uint256 _bondDepletionFloorPercent) external onlyOperator {
require(_bondDepletionFloorPercent >= 500 && _bondDepletionFloorPercent <= 10000, "out of range"); // [5%, 100%]
require(_bondDepletionFloorPercent >= 500 && _bondDepletionFloorPercent <= 10000, "out of range"); // [5%, 100%]
bondDepletionFloorPercent = _bondDepletionFloorPercent;
bondDepletionFloorPercent = _bondDepletionFloorPercent;
}
}


function setMaxSupplyContractionPercent(uint256 _maxSupplyContractionPercent) external onlyOperator {
function setMaxSupplyContractionPercent(uint256 _maxSupplyContractionPercent) external onlyOperator {
require(_maxSupplyContractionPercent >= 100 && _maxSupplyContractionPercent <= 1500, "out of range"); // [0.1%, 15%]
require(_maxSupplyContractionPercent >= 100 && _maxSupplyContractionPercent <= 1500, "out of range"); // [0.1%, 15%]
maxSupplyContractionPercent = _maxSupplyContractionPercent;
maxSupplyContractionPercent = _maxSupplyContractionPercent;
}
}


function setMaxDebtRatioPercent(uint256 _maxDebtRatioPercent) external onlyOperator {
function setMaxDebtRatioPercent(uint256 _maxDebtRatioPercent) external onlyOperator {
require(_maxDebtRatioPercent >= 1000 && _maxDebtRatioPercent <= 10000, "out of range"); // [10%, 100%]
require(_maxDebtRatioPercent >= 1000 && _maxDebtRatioPercent <= 10000, "out of range"); // [10%, 100%]
maxDebtRatioPercent = _maxDebtRatioPercent;
maxDebtRatioPercent = _maxDebtRatioPercent;
}
}


function setBootstrap(uint256 _bootstrapEpochs, uint256 _bootstrapSupplyExpansionPercent) external onlyOperator {
function setBootstrap(uint256 _bootstrapEpochs, uint256 _bootstrapSupplyExpansionPercent) external onlyOperator {
require(_bootstrapEpochs <= 120, "_bootstrapEpochs: out of range"); // <= 1 month
require(_bootstrapEpochs <= 120, "_bootstrapEpochs: out of range"); // <= 1 month
require(_bootstrapSupplyExpansionPercent >= 100 && _bootstrapSupplyExpansionPercent <= 1000, "_bootstrapSupplyExpansionPercent: out of range"); // [1%, 10%]
require(_bootstrapSupplyExpansionPercent >= 100 && _bootstrapSupplyExpansionPercent <= 1000, "_bootstrapSupplyExpansionPercent: out of range"); // [1%, 10%]
bootstrapEpochs = _bootstrapEpochs;
bootstrapEpochs = _bootstrapEpochs;
bootstrapSupplyExpansionPercent = _bootstrapSupplyExpansionPercent;
bootstrapSupplyExpansionPercent = _bootstrapSupplyExpansionPercent;
}
}

//===============================================================================================================================================
function setExtraFunds(
function setExtraFunds(
// DAO FUND - 12%
// DEVS WALLET - 3%
// TEAM WALLET - 5%
address _daoFund,
address _daoFund,
uint256 _daoFundSharedPercent,
uint256 _daoFundSharedPercent,
address _devFund,
address _devFund,
uint256 _devFundSharedPercent
uint256 _devFundSharedPercent,
address _teamFund,
uint256 _teamFundSharedPercent
) external onlyOperator {
) external onlyOperator {
require(_daoFund != address(0), "zero");
require(_daoFund != address(0), "zero");
require(_daoFundSharedPercent <= 3000, "out of range"); // <= 30%
require(_daoFundSharedPercent <= 1500, "out of range"); // <= 15%
require(_devFund != address(0), "zero");
require(_devFund != address(0), "zero");
require(_devFundSharedPercent <= 1000, "out of range"); // <= 10%
require(_devFundSharedPercent <= 350, "out of range"); // <= 3.5%
require(_teamFund != address(0), "zero");
require(_teamFundSharedPercent <= 550, "out of range"); // <= 5.5%

daoFund = _daoFund;
daoFund = _daoFund;
daoFundSharedPercent = _daoFundSharedPercent;
daoFundSharedPercent = _daoFundSharedPercent;
devFund = _devFund;
devFund = _devFund;
devFundSharedPercent = _devFundSharedPercent;
devFundSharedPercent = _devFundSharedPercent;
teamFund = _teamFund;
teamFundSharedPercent = _teamFundSharedPercent;
}
}


function setMaxDiscountRate(uint256 _maxDiscountRate) external onlyOperator {
function setMaxDiscountRate(uint256 _maxDiscountRate) external onlyOperator {
maxDiscountRate = _maxDiscountRate;
maxDiscountRate = _maxDiscountRate;
}
}


function setMaxPremiumRate(uint256 _maxPremiumRate) external onlyOperator {
function setMaxPremiumRate(uint256 _maxPremiumRate) external onlyOperator {
maxPremiumRate = _maxPremiumRate;
maxPremiumRate = _maxPremiumRate;
}
}


function setDiscountPercent(uint256 _discountPercent) external onlyOperator {
function setDiscountPercent(uint256 _discountPercent) external onlyOperator {
require(_discountPercent <= 20000, "_discountPercent is over 200%");
require(_discountPercent <= 20000, "_discountPercent is over 200%");
discountPercent = _discountPercent;
discountPercent = _discountPercent;
}
}


function setPremiumThreshold(uint256 _premiumThreshold) external onlyOperator {
function setPremiumThreshold(uint256 _premiumThreshold) external onlyOperator {
require(_premiumThreshold >= tombPriceCeiling, "_premiumThreshold exceeds tombPriceCeiling");
require(_premiumThreshold >= basedPriceCeiling, "_premiumThreshold exceeds basedPriceCeiling");
require(_premiumThreshold <= 150, "_premiumThreshold is higher than 1.5");
require(_premiumThreshold <= 150, "_premiumThreshold is higher than 1.5");
premiumThreshold = _premiumThreshold;
premiumThreshold = _premiumThreshold;
}
}


function setPremiumPercent(uint256 _premiumPercent) external onlyOperator {
function setPremiumPercent(uint256 _premiumPercent) external onlyOperator {
require(_premiumPercent <= 20000, "_premiumPercent is over 200%");
require(_premiumPercent <= 20000, "_premiumPercent is over 200%");
premiumPercent = _premiumPercent;
premiumPercent = _premiumPercent;
}
}


function setMintingFactorForPayingDebt(uint256 _mintingFactorForPayingDebt) external onlyOperator {
function setMintingFactorForPayingDebt(uint256 _mintingFactorForPayingDebt) external onlyOperator {
require(_mintingFactorForPayingDebt >= 10000 && _mintingFactorForPayingDebt <= 20000, "_mintingFactorForPayingDebt: out of range"); // [100%, 200%]
require(_mintingFactorForPayingDebt >= 10000 && _mintingFactorForPayingDebt <= 20000, "_mintingFactorForPayingDebt: out of range"); // [100%, 200%]
mintingFactorForPayingDebt = _mintingFactorForPayingDebt;
mintingFactorForPayingDebt = _mintingFactorForPayingDebt;
}
}


/* ========== MUTABLE FUNCTIONS ========== */
/* ========== MUTABLE FUNCTIONS ========== */


function _updateTombPrice() internal {
function _updateBasedPrice() internal {
try IOracle(tombOracle).update() {} catch {}
try IOracle(basedOracle).update() {} catch {}
}
}


function getTombCirculatingSupply() public view returns (uint256) {
function getBasedCirculatingSupply() public view returns (uint256) {
IERC20 tombErc20 = IERC20(tomb);
IERC20 basedErc20 = IERC20(based);
uint256 totalSupply = tombErc20.totalSupply();
uint256 totalSupply = basedErc20.totalSupply();
uint256 balanceExcluded = 0;
uint256 balanceExcluded = 0;
for (uint8 entryId = 0; entryId < excludedFromTotalSupply.length; ++entryId) {
for (uint8 entryId = 0; entryId < excludedFromTotalSupply.length; ++entryId) {
balanceExcluded = balanceExcluded.add(tombErc20.balanceOf(excludedFromTotalSupply[entryId]));
balanceExcluded = balanceExcluded.add(basedErc20.balanceOf(excludedFromTotalSupply[entryId]));
}
}
return totalSupply.sub(balanceExcluded);
return totalSupply.sub(balanceExcluded);
}
}


function buyBonds(uint256 _tombAmount, uint256 targetPrice) external onlyOneBlock checkCondition checkOperator {
function buyBonds(uint256 _basedAmount, uint256 targetPrice) external onlyOneBlock checkCondition checkOperator {
require(_tombAmount > 0, "Treasury: cannot purchase bonds with zero amount");
require(_basedAmount > 0, "Treasury: cannot purchase bonds with zero amount");


uint256 tombPrice = getTombPrice();
uint256 basedPrice = getBasedPrice();
require(tombPrice == targetPrice, "Treasury: TOMB price moved");
require(basedPrice == targetPrice, "Treasury: BASED price moved");
require(
require(
tombPrice < tombPriceOne, // price < $1
basedPrice < basedPriceOne, // price < $1
"Treasury: tombPrice not eligible for bond purchase"
"Treasury: basedPrice not eligible for bond purchase"
);
);


require(_tombAmount <= epochSupplyContractionLeft, "Treasury: not enough bond left to purchase");
require(_basedAmount <= epochSupplyContractionLeft, "Treasury: not enough bond left to purchase");


uint256 _rate = getBondDiscountRate();
uint256 _rate = getBondDiscountRate();
require(_rate > 0, "Treasury: invalid bond rate");
require(_rate > 0, "Treasury: invalid bond rate");


uint256 _bondAmount = _tombAmount.mul(_rate).div(1e18);
uint256 _bondAmount = _basedAmount.mul(_rate).div(1e18);
uint256 tombSupply = getTombCirculatingSupply();
uint256 basedSupply = getBasedCirculatingSupply();
uint256 newBondSupply = IERC20(tbond).totalSupply().add(_bondAmount);
uint256 newBondSupply = IERC20(bbond).totalSupply().add(_bondAmount);
require(newBondSupply <= tombSupply.mul(maxDebtRatioPercent).div(10000), "over max debt ratio");
require(newBondSupply <= basedSupply.mul(maxDebtRatioPercent).div(10000), "over max debt ratio");


IBasisAsset(tomb).burnFrom(msg.sender, _tombAmount);
IBasisAsset(based).burnFrom(msg.sender, _basedAmount);
IBasisAsset(tbond).mint(msg.sender, _bondAmount);
IBasisAsset(bbond).mint(msg.sender, _bondAmount);


epochSupplyContractionLeft = epochSupplyContractionLeft.sub(_tombAmount);
epochSupplyContractionLeft = epochSupplyContractionLeft.sub(_basedAmount);
_updateTombPrice();
_updateBasedPrice();


emit BoughtBonds(msg.sender, _tombAmount, _bondAmount);
emit BoughtBonds(msg.sender, _basedAmount, _bondAmount);
}
}


function redeemBonds(uint256 _bondAmount, uint256 targetPrice) external onlyOneBlock checkCondition checkOperator {
function redeemBonds(uint256 _bondAmount, uint256 targetPrice) external onlyOneBlock checkCondition checkOperator {
require(_bondAmount > 0, "Treasury: cannot redeem bonds with zero amount");
require(_bondAmount > 0, "Treasury: cannot redeem bonds with zero amount");


uint256 tombPrice = getTombPrice();
uint256 basedPrice = getBasedPrice();
require(tombPrice == targetPrice, "Treasury: TOMB price moved");
require(basedPrice == targetPrice, "Treasury: BASED price moved");
require(
require(
tombPrice > tombPriceCeiling, // price > $1.01
basedPrice > basedPriceCeiling, // price > $1.01
"Treasury: tombPrice not eligible for bond purchase"
"Treasury: basedPrice not eligible for bond purchase"
);
);


uint256 _rate = getBondPremiumRate();
uint256 _rate = getBondPremiumRate();
require(_rate > 0, "Treasury: invalid bond rate");
require(_rate > 0, "Treasury: invalid bond rate");


uint256 _tombAmount = _bondAmount.mul(_rate).div(1e18);
uint256 _basedAmount = _bondAmount.mul(_rate).div(1e18);
require(IERC20(tomb).balanceOf(address(this)) >= _tombAmount, "Treasury: treasury has no more budget");
require(IERC20(based).balanceOf(address(this)) >= _basedAmount, "Treasury: treasury has no more budget");


seigniorageSaved = seigniorageSaved.sub(Math.min(seigniorageSaved, _tombAmount));
seigniorageSaved = seigniorageSaved.sub(Math.min(seigniorageSaved, _basedAmount));


IBasisAsset(tbond).burnFrom(msg.sender, _bondAmount);
IBasisAsset(bbond).burnFrom(msg.sender, _bondAmount);
IERC20(tomb).safeTransfer(msg.sender, _tombAmount);
IERC20(based).safeTransfer(msg.sender, _basedAmount);


_updateTombPrice();
_updateBasedPrice();


emit RedeemedBonds(msg.sender, _tombAmount, _bondAmount);
emit RedeemedBonds(msg.sender, _basedAmount, _bondAmount);
}
}


function _sendToMasonry(uint256 _amount) internal {
function _sendToAcropolis(uint256 _amount) internal {
IBasisAsset(tomb).mint(address(this), _amount);
IBasisAsset(based).mint(address(this), _amount);


uint256 _daoFundSharedAmount = 0;
uint256 _daoFundSharedAmount = 0;
if (daoFundSharedPercent > 0) {
if (daoFundSharedPercent > 0) {
_daoFundSharedAmount = _amount.mul(daoFundSharedPercent).div(10000);
_daoFundSharedAmount = _amount.mul(daoFundSharedPercent).div(10000);
IERC20(tomb).transfer(daoFund, _daoFundSharedAmount);
IERC20(based).transfer(daoFund, _daoFundSharedAmount);
emit DaoFundFunded(now, _daoFundSharedAmount);
emit DaoFundFunded(block.timestamp, _daoFundSharedAmount);
}
}


uint256 _devFundSharedAmount = 0;
uint256 _devFundSharedAmount = 0;
if (devFundSharedPercent > 0) {
if (devFundSharedPercent > 0) {
_devFundSharedAmount = _amount.mul(devFundSharedPercent).div(10000);
_devFundSharedAmount = _amount.mul(devFundSharedPercent).div(10000);
IERC20(tomb).transfer(devFund, _devFundSharedAmount);
IERC20(based).transfer(devFund, _devFundSharedAmount);
emit DevFundFunded(now, _devFundSharedAmount);
emit DevFundFunded(block.timestamp, _devFundSharedAmount);
}
}


_amount = _amount.sub(_daoFundSharedAmount).sub(_devFundSharedAmount);
uint256 _teamFundSharedAmount = 0;
if (teamFundSharedPercent > 0) {
_teamFundSharedAmount = _amount.mul(teamFundSharedPercent).div(10000);
IERC20(based).transfer(teamFund, _teamFundSharedAmount);
emit TeamFundFunded(block.timestamp, _teamFundSharedAmount);
}


IERC20(tomb).safeApprove(masonry, 0);
_amount = _amount.sub(_daoFundSharedAmount).sub(_devFundSharedAmount).sub(_teamFundSharedAmount);
IERC20(tomb).safeApprove(masonry, _amount);

IMasonry(masonry).allocateSeigniorage(_amount);
IERC20(based).safeApprove(acropolis, 0);
emit MasonryFunded(now, _amount);
IERC20(based).safeApprove(acropolis, _amount);
IAcropolis(acropolis).allocateSeigniorage(_amount);
emit AcropolisFunded(block.timestamp, _amount);
}
}


function _calculateMaxSupplyExpansionPercent(uint256 _tombSupply) internal returns (uint256) {
function _calculateMaxSupplyExpansionPercent(uint256 _basedSupply) internal returns (uint256) {
for (uint8 tierId = 8; tierId >= 0; --tierId) {
for (uint8 tierId = 6; tierId >= 0; --tierId) {
if (_tombSupply >= supplyTiers[tierId]) {
if (_basedSupply >= supplyTiers[tierId]) {
maxSupplyExpansionPercent = maxExpansionTiers[tierId];
maxSupplyExpansionPercent = maxExpansionTiers[tierId];
break;
break;
}
}
}
}
return maxSupplyExpansionPercent;
return maxSupplyExpansionPercent;
}
}


function allocateSeigniorage() external onlyOneBlock checkCondition checkEpoch checkOperator {
function allocateSeigniorage() external onlyOneBlock checkCondition checkEpoch checkOperator {
_updateTombPrice();
_updateBasedPrice();
previousEpochTombPrice = getTombPrice();
previousEpochBasedPrice = getBasedPrice();
uint256 tombSupply = getTombCirculatingSupply().sub(seigniorageSaved);
uint256 basedSupply = getBasedCirculatingSupply().sub(seigniorageSaved);
if (epoch < bootstrapEpochs) {
if (epoch < bootstrapEpochs) {
// 28 first epochs with 4.5% expansion
// 14 first epochs with 6% expansion
_sendToMasonry(tombSupply.mul(bootstrapSupplyExpansionPercent).div(10000));
_sendToAcropolis(basedSupply.mul(bootstrapSupplyExpansionPercent).div(10000));
} else {
} else {
if (previousEpochTombPrice > tombPriceCeiling) {
if (previousEpochBasedPrice > basedPriceCeiling) {
// Expansion ($TOMB Price > 1 $FTM): there is some seigniorage to be allocated
// Expansion ($BASED Price > 1 $FTM): there is some seigniorage to be allocated
uint256 bondSupply = IERC20(tbond).totalSupply();
uint256 bondSupply = IERC20(bbond).totalSupply();
uint256 _percentage = previousEpochTombPrice.sub(tombPriceOne);
uint256 _percentage = previousEpochBasedPrice.sub(basedPriceOne);
uint256 _savedForBond;
uint256 _savedForBond;
uint256 _savedForMasonry;
uint256 _savedForAcropolis;
uint256 _mse = _calculateMaxSupplyExpansionPercent(tombSupply).mul(1e14);
uint256 _mse = _calculateMaxSupplyExpansionPercent(basedSupply).mul(1e14);
if (_percentage > _mse) {
if (_percentage > _mse) {
_percentage = _mse;
_percentage = _mse;
}
}
if (seigniorageSaved >= bondSupply.mul(bondDepletionFloorPercent).div(10000)) {
if (seigniorageSaved >= bondSupply.mul(bondDepletionFloorPercent).div(10000)) {
// saved enough to pay debt, mint as usual rate
// saved enough to pay debt, mint as usual rate
_savedForMasonry = tombSupply.mul(_percentage).div(1e18);
_savedForAcropolis = basedSupply.mul(_percentage).div(1e18);
} else {
} else {
// have not saved enough to pay debt, mint more
// have not saved enough to pay debt, mint more
uint256 _seigniorage = tombSupply.mul(_percentage).div(1e18);
uint256 _seigniorage = basedSupply.mul(_percentage).div(1e18);
_savedForMasonry = _seigniorage.mul(seigniorageExpansionFloorPercent).div(10000);
_savedForAcropolis = _seigniorage.mul(seigniorageExpansionFloorPercent).div(10000);
_savedForBond = _seigniorage.sub(_savedForMasonry);
_savedForBond = _seigniorage.sub(_savedForAcropolis);
if (mintingFactorForPayingDebt > 0) {
if (mintingFactorForPayingDebt > 0) {
_savedForBond = _savedForBond.mul(mintingFactorForPayingDebt).div(10000);
_savedForBond = _savedForBond.mul(mintingFactorForPayingDebt).div(10000);
}
}
}
}
if (_savedForMasonry > 0) {
if (_savedForAcropolis > 0) {
_sendToMasonry(_savedForMasonry);
_sendToAcropolis(_savedForAcropolis);
}
}
if (_savedForBond > 0) {
if (_savedForBond > 0) {
seigniorageSaved = seigniorageSaved.add(_savedForBond);
seigniorageSaved = seigniorageSaved.add(_savedForBond);
IBasisAsset(tomb).mint(address(this), _savedForBond);
IBasisAsset(based).mint(address(this), _savedForBond);
emit TreasuryFunded(now, _savedForBond);
emit TreasuryFunded(block.timestamp, _savedForBond);
}
}
}
}
}
}
}
}
//===================================================================================================================================


function governanceRecoverUnsupported(
function governanceRecoverUnsupported(
IERC20 _token,
IERC20 _token,
uint256 _amount,
uint256 _amount,
address _to
address _to
) external onlyOperator {
) external onlyOperator {
// do not allow to drain core tokens
// do not allow to drain core tokens
require(address(_token) != address(tomb), "tomb");
require(address(_token) != address(based), "based");
require(address(_token) != address(tbond), "bond");
require(address(_token) != address(bbond), "bond");
require(address(_token) != address(tshare), "share");
require(address(_token) != address(bshare), "share");
_token.safeTransfer(_to, _amount);
_token.safeTransfer(_to, _amount);
}
}


function masonrySetOperator(address _operator) external onlyOperator {
function acropolisSetOperator(address _operator) external onlyOperator {
IMasonry(masonry).setOperator(_operator);
IAcropolis(acropolis).setOperator(_operator);
}
}


function masonrySetLockUp(uint256 _withdrawLockupEpochs, uint256 _rewardLockupEpochs) external onlyOperator {
function acropolisSetLockUp(uint256 _withdrawLockupEpochs, uint256 _rewardLockupEpochs) external onlyOperator {
IMasonry(masonry).setLockUp(_withdrawLockupEpochs, _rewardLockupEpochs);
IAcropolis(acropolis).setLockUp(_withdrawLockupEpochs, _rewardLockupEpochs);
}
}


function masonryAllocateSeigniorage(uint256 amount) external onlyOperator {
function acropolisAllocateSeigniorage(uint256 amount) external onlyOperator {
IMasonry(masonry).allocateSeigniorage(amount);
IAcropolis(acropolis).allocateSeigniorage(amount);
}
}


function masonryGovernanceRecoverUnsupported(
function acropolisGovernanceRecoverUnsupported(
address _token,
address _token,
uint256 _amount,
uint256 _amount,
address _to
address _to
) external onlyOperator {
) external onlyOperator {
IMasonry(masonry).governanceRecoverUnsupported(_token, _amount, _to);
IAcropolis(acropolis).governanceRecoverUnsupported(_token, _amount, _to);
}
}
}
}