Comparing sensitive data, confidential files or internal emails?

Most legal and privacy policies prohibit uploading sensitive data online. Diffchecker Desktop ensures your confidential information never leaves your computer. Work offline and compare documents securely.

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);
}
}
}
}