// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=0.8.0;/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation./// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.abstractcontractERC20{
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/eventTransfer(addressindexedfrom, addressindexed to, uint256 amount);
eventApproval(addressindexed owner, addressindexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/stringpublic name;
stringpublic symbol;
uint8publicimmutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/uint256public totalSupply;
mapping(address=>uint256) public balanceOf;
mapping(address=>mapping(address=>uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/uint256internalimmutable INITIAL_CHAIN_ID;
bytes32internalimmutable INITIAL_DOMAIN_SEPARATOR;
mapping(address=>uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/constructor(stringmemory _name,
stringmemory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID =block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/functionapprove(address spender, uint256 amount) publicvirtualreturns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
returntrue;
}
functiontransfer(address to, uint256 amount) publicvirtualreturns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
returntrue;
}
functiontransferFrom(addressfrom,
address to,
uint256 amount
) publicvirtualreturns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.if (allowed !=type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
returntrue;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/functionpermit(address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) publicvirtual{
require(deadline >=block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing// the owner's nonce which cannot realistically overflow.unchecked {
address recoveredAddress =ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress !=address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
functionDOMAIN_SEPARATOR() publicviewvirtualreturns (bytes32) {
returnblock.chainid== INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
functioncomputeDomainSeparator() internalviewvirtualreturns (bytes32) {
returnkeccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/function_mint(address to, uint256 amount) internalvirtual{
totalSupply += amount;
// Cannot overflow because the sum of all user// balances can't exceed the max uint256 value.unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function_burn(addressfrom, uint256 amount) internalvirtual{
balanceOf[from] -= amount;
// Cannot underflow because a user's balance// will never be larger than the total supply.unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
Contract Source Code
File 2 of 6: FullMath.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;/// @title Contains 512-bit math functions/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bitslibraryFullMath{
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0/// @param a The multiplicand/// @param b The multiplier/// @param denominator The divisor/// @return result The 256-bit result/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldivfunctionmulDiv(uint256 a, uint256 b, uint256 denominator) internalpurereturns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b// Compute the product mod 2**256 and mod 2**256 - 1// then use the Chinese Remainder Theorem to reconstruct// the 512 bit result. The result is stored in two 256// variables such that product = prod1 * 2**256 + prod0uint256 prod0; // Least significant 256 bits of the productuint256 prod1; // Most significant 256 bits of the productassembly {
let mm :=mulmod(a, b, not(0))
prod0 :=mul(a, b)
prod1 :=sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 divisionif (prod1 ==0) {
require(denominator >0);
assembly {
result :=div(prod0, denominator)
}
return result;
}
// Make sure the result is less than 2**256.// Also prevents denominator == 0require(denominator > prod1);
///////////////////////////////////////////////// 512 by 256 division.///////////////////////////////////////////////// Make division exact by subtracting the remainder from [prod1 prod0]// Compute remainder using mulmoduint256 remainder;
assembly {
remainder :=mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit numberassembly {
prod1 :=sub(prod1, gt(remainder, prod0))
prod0 :=sub(prod0, remainder)
}
// Factor powers of two out of denominator// Compute largest power of two divisor of denominator.// Always >= 1.uint256 twos = (0- denominator) & denominator;
// Divide denominator by power of twoassembly {
denominator :=div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of twoassembly {
prod0 :=div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need// to flip `twos` such that it is 2**256 / twos.// If twos is zero, then it becomes oneassembly {
twos :=add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
// Invert denominator mod 2**256// Now that denominator is an odd number, it has an inverse// modulo 2**256 such that denominator * inv = 1 mod 2**256.// Compute the inverse by starting with a seed that is correct// correct for four bits. That is, denominator * inv = 1 mod 2**4uint256 inv = (3* denominator) ^2;
// Now use Newton-Raphson iteration to improve the precision.// Thanks to Hensel's lifting lemma, this also works in modular// arithmetic, doubling the correct bits in each step.
inv *=2- denominator * inv; // inverse mod 2**8
inv *=2- denominator * inv; // inverse mod 2**16
inv *=2- denominator * inv; // inverse mod 2**32
inv *=2- denominator * inv; // inverse mod 2**64
inv *=2- denominator * inv; // inverse mod 2**128
inv *=2- denominator * inv; // inverse mod 2**256// Because the division is now exact we can divide by multiplying// with the modular inverse of denominator. This will give us the// correct result modulo 2**256. Since the precoditions guarantee// that the outcome is less than 2**256, this is the final result.// We don't need to compute the high bits of the result and prod1// is no longer required.
result = prod0 * inv;
return result;
}
}
/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0/// @param a The multiplicand/// @param b The multiplier/// @param denominator The divisor/// @return result The 256-bit resultfunctionmulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internalpurereturns (uint256 result) {
unchecked {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) >0) {
require(result <type(uint256).max);
result++;
}
}
}
}
// SPDX-License-Identifier: MIT///@title Koto ERC20 Token///@author Izanagi Dev///@notice A stripped down ERC20 tax token that implements automated and continious monetary policy decisions.///@dev Bonds are the ERC20 token in exchange for Ether. Unsold bonds with automatically carry over to the next day./// The bonding schedule is set to attempt to sell all of the tokens held within the contract in 1 day intervals. Taking a snapshot/// of the amount currently held within the contract at the start of the next internal period, using this amount as the capcipty to be sold./// Socials/// Telegram: https://t.me/KotoPortalpragmasolidity =0.8.22;import {PricingLibrary} from"./PricingLibrary.sol";
import {SafeTransferLib} from"lib/solmate/src/utils/SafeTransferLib.sol";
import {FullMath} from"./libraries/FullMath.sol";
import {IERC20Minimal} from"./interfaces/IERC20Minimal.sol";
contractKotoV2{
// ========================== STORAGE ========================== \\mapping(address=>uint256) private _balances;
mapping(address=>mapping(address=>uint256)) private _allowances;
mapping(address=>bool) private _excluded;
mapping(address=>bool) private _amms;
uint256private _totalSupply;
// ====================== ETH BOND STORAGE ===================== \\
PricingLibrary.Adjustment private adjustment;
PricingLibrary.Data private data;
PricingLibrary.Market private market;
PricingLibrary.Term private term;
// ====================== LP BOND STORAGE ====================== \\
PricingLibrary.Adjustment private lpAdjustment;
PricingLibrary.Data private lpData;
PricingLibrary.Market private lpMarket;
PricingLibrary.Term private lpTerm;
uint256 ethCapacityNext;
uint256 lpCapacityNext;
uint8private locked;
boolprivate launched;
// =================== CONSTANTS / IMMUTABLES =================== \\stringprivateconstant NAME ="Koto V2";
stringprivateconstant SYMBOL ="KOTOV2";
uint8privateconstant DECIMALS =18;
///@dev flat 5% tax for buys and sellsuint8privateconstant FEE =50;
boolprivateimmutable zeroForOne;
addressprivateconstant UNISWAP_V2_ROUTER =0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
addressprivateconstant UNISWAP_V2_FACTORY =0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
addressprivateconstant WETH =0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
addressprivateconstant OWNER =0x946eF43867225695E29241813A8F41519634B36b;
addressprivateconstant BOND_DEPOSITORY =0x298ECA8683000B3911B2e7Dd07FD496D8019043E;
addressprivateimmutable pair;
addressprivateimmutable token0;
addressprivateimmutable token1;
uint256privateconstant INTERVAL =86400; // 1 day in seconds// ========================== MODIFIERS ========================== \\modifierlock() {
if (locked ==2) revert Reentrancy();
locked =2;
_;
locked =1;
}
// ========================= CONTRUCTOR ========================= \\constructor() {
pair = _createUniswapV2Pair(address(this), WETH);
_excluded[OWNER] =true;
_excluded[BOND_DEPOSITORY] =true;
_excluded[address(this)] =true;
_amms[pair] =true;
_mint(OWNER, 8_500_000e18); //
(token0, token1) = _getTokens(pair);
zeroForOne =address(this) == token0 ? true : false;
_allowances[address(this)][UNISWAP_V2_ROUTER] =type(uint256).max;
///@dev set term conclusion to type uint48 max to prevent bonds being created before opening them to the public
term.conclusion =type(uint48).max;
}
// ==================== EXTERNAL FUNCTIONS ===================== \\functiontransfer(address _to, uint256 _value) publicreturns (bool success) {
if (_to ==address(0) || _value ==0) revert InvalidTransfer();
_transfer(msg.sender, _to, _value);
returntrue;
}
functiontransferFrom(address _from, address _to, uint256 _value) publicreturns (bool success) {
if (_to ==address(0) || _value ==0) revert InvalidTransfer();
if (_from !=msg.sender) {
if (_allowances[_from][msg.sender] < _value) revert InsufficentAllowance();
_allowances[_from][msg.sender] -= _value;
}
_transfer(_from, _to, _value);
returntrue;
}
functionapprove(address _spender, uint256 _value) publicreturns (bool success) {
address owner =msg.sender;
_allowances[owner][_spender] = _value;
returntrue;
}
///@notice exchange ETH for Koto tokens at the current bonding price///@dev bonds are set on 1 day intervals with 4 hour deposit intervals and 30 minute tune intervals.functionbond() publicpayablelockreturns (uint256 payout) {
// If the previous market has ended create a new market.if (block.timestamp> term.conclusion) {
_create();
}
if (market.capacity !=0) {
// Cache variables for later use to minimize storage calls
PricingLibrary.Market memory _market = market;
PricingLibrary.Term memory _term = term;
PricingLibrary.Data memory _data = data;
PricingLibrary.Adjustment memory adjustments = adjustment;
uint256 _supply = _totalSupply;
uint48 time =uint48(block.timestamp);
// Can pass in structs here as nothing has been updated yet
(_market, _data, _term, adjustments) = PricingLibrary.decay(data, _market, _term, adjustments);
uint256 price = PricingLibrary.marketPrice(_term.controlVariable, _market.totalDebt, _supply);
payout = (msg.value*1e18/ price);
if (payout > market.maxPayout) revert MaxPayout();
// Update market variables
_market.capacity -=uint96(payout);
_market.purchased +=uint96(msg.value);
_market.sold +=uint96(payout);
_market.totalDebt +=uint96(payout);
bool success = _bond(msg.sender, payout);
if (!success) revert BondFailed();
emit Bond(msg.sender, payout, price);
//Touches market, data, terms, and adjustments
(_market, _term, _data, adjustments) =
PricingLibrary.tune(time, _market, _term, _data, adjustments, _supply);
// Write changes to storage.
market = _market;
term = _term;
data = _data;
adjustment = adjustments;
} else {
//If bonds are not available refund the eth sent to the contract
SafeTransferLib.safeTransferETH(msg.sender, msg.value);
}
}
functionbondLp(uint256 _lpAmount) publiclockreturns (uint256 payout) {
// If the previous market has ended create a new market.if (block.timestamp> term.conclusion) {
_createLpMarket();
}
if (lpMarket.capacity !=0) {
IERC20Minimal(pair).transferFrom(msg.sender, address(BOND_DEPOSITORY), _lpAmount);
// Cache variables for later use to minimize storage calls
PricingLibrary.Market memory _market = lpMarket;
PricingLibrary.Term memory _term = lpTerm;
PricingLibrary.Data memory _data = lpData;
PricingLibrary.Adjustment memory adjustments = lpAdjustment;
uint256 _supply = _totalSupply;
uint48 time =uint48(block.timestamp);
// Can pass in structs here as nothing has been updated yet
(_market, _data, _term, adjustments) = PricingLibrary.decay(lpData, _market, _term, adjustments);
uint256 price = PricingLibrary.marketPrice(_term.controlVariable, _market.totalDebt, _supply);
payout = (_lpAmount *1e18/ price);
if (payout > lpMarket.maxPayout) revert MaxPayout();
// Update market variables
_market.capacity -=uint96(payout);
_market.purchased +=uint96(_lpAmount);
_market.sold +=uint96(payout);
_market.totalDebt +=uint96(payout);
bool success = _bond(msg.sender, payout);
if (!success) revert BondFailed();
emit Bond(msg.sender, payout, price);
//Touches market, data, terms, and adjustments
(_market, _term, _data, adjustments) =
PricingLibrary.tune(time, _market, _term, _data, adjustments, _supply);
// Write changes to storage.
lpMarket = _market;
lpTerm = _term;
lpData = _data;
lpAdjustment = adjustments;
}
}
///@notice burn Koto tokens in exchange for a piece of the underlying reserves///@param amount The amount of Koto tokens to redeem///@return payout The amount of ETH received in exchange for the Koto tokensfunctionredeem(uint256 amount) externalreturns (uint256 payout) {
// Underlying reserves per tokenuint256 price = (address(this).balance*1e18) / _totalSupply;
payout = (price * amount) /1e18;
_burn(msg.sender, amount);
SafeTransferLib.safeTransferETH(msg.sender, payout);
emit Redeem(msg.sender, amount, payout, price);
}
///@notice burn Koto tokens, without redemption///@param amount the amount of Koto to burnfunctionburn(uint256 amount) externalreturns (bool success) {
_burn(msg.sender, amount);
success =true;
emit Transfer(msg.sender, address(0), amount);
}
// ==================== EXTERNAL VIEW FUNCTIONS ===================== \\///@notice get the tokens namefunctionname() publicpurereturns (stringmemory) {
return NAME;
}
///@notice get the tokens symbolfunctionsymbol() publicpurereturns (stringmemory) {
return SYMBOL;
}
///@notice get the tokens decimalsfunctiondecimals() publicpurereturns (uint8) {
return DECIMALS;
}
///@notice get the tokens total supplyfunctiontotalSupply() publicviewreturns (uint256) {
return _totalSupply;
}
///@notice get the current balance of a user///@param _owner the user whos balance you want to checkfunctionbalanceOf(address _owner) publicviewreturns (uint256) {
return _balances[_owner];
}
///@notice get current approved amount for transfer from another party///@param owner the current owner of the tokens///@param spender the user who has approval (or not) to spend the owners tokensfunctionallowance(address owner, address spender) publicviewvirtualreturns (uint256) {
return _allowances[owner][spender];
}
///@notice return the Uniswap V2 Pair addressfunctionpool() externalviewreturns (address) {
return pair;
}
///@notice get the owner of the contract///@dev ownership is nontransferable and limited to opening trade, exclusion / inclusion,s and increasing liquidityfunctionownership() externalpurereturns (address) {
return OWNER;
}
///@notice the current price a bondfunctionbondPrice() externalviewreturns (uint256) {
return PricingLibrary.marketPrice(term.controlVariable, market.totalDebt, _totalSupply);
}
functionbondPriceLp() externalviewreturns (uint256) {
return PricingLibrary.marketPrice(lpTerm.controlVariable, lpMarket.totalDebt, _totalSupply);
}
///@notice return the current redemption price for 1 uint of Koto.functionredemptionPrice() externalviewreturns (uint256) {
return ((address(this).balance*1e18) / _totalSupply);
}
functionmarketInfo()
externalviewreturns (PricingLibrary.Market memory, PricingLibrary.Term memory, PricingLibrary.Data memory)
{
return (market, term, data);
}
functionlpMarketInfo()
externalviewreturns (PricingLibrary.Market memory, PricingLibrary.Term memory, PricingLibrary.Data memory)
{
return (lpMarket, lpTerm, lpData);
}
functiondepository() externalpurereturns (address) {
return BOND_DEPOSITORY;
}
// ========================= ADMIN FUNCTIONS ========================= \\///@notice remove a given address from fees and limits///@param user the user to exclude from fees///@dev this is a one way street so once a user has been excluded they can not then be removedfunctionexclude(address user) external{
if (msg.sender!= OWNER) revert OnlyOwner();
_excluded[user] =true;
emit UserExcluded(user);
}
///@notice add a amm pool / pair///@param _pool the address of the pool / pair to addfunctionaddAmm(address _pool) external{
if (msg.sender!= OWNER) revert OnlyOwner();
_amms[_pool] =true;
emit AmmAdded(_pool);
}
///@notice seed the initial liquidity from this contract.functionlaunch() external{
if (msg.sender!= OWNER) revert OnlyOwner();
if (launched) revert AlreadyLaunched();
_addInitialLiquidity();
launched =true;
emit Launched(block.timestamp);
}
///@notice opens the bond market///@dev the liquidity pool must already be launched and initialized. As well as tokens sent to this contract from/// the bond depository.functionopen() external{
if (msg.sender!= OWNER) revert OnlyOwner();
_create();
_createLpMarket();
emit OpenBondMarket(block.timestamp);
}
///TODO: change back to permissioned prior to launch.functioncreate(uint256 ethBondAmount, uint256 lpBondAmount) external{
if (msg.sender!= OWNER &&msg.sender!= BOND_DEPOSITORY) revert InvalidSender();
uint256 total = ethBondAmount + lpBondAmount;
transferFrom(msg.sender, address(this), total);
ethCapacityNext = ethBondAmount;
lpCapacityNext = lpBondAmount;
}
// ========================= INTERNAL FUNCTIONS ========================= \\///@notice create the Uniswap V2 Pair///@param _token0 token 0 of the pair///@param _token1 token 1 of the pair///@return _pair the pair addressfunction_createUniswapV2Pair(address _token0, address _token1) privatereturns (address _pair) {
assembly {
let ptr :=mload(0x40)
mstore(ptr, 0xc9c6539600000000000000000000000000000000000000000000000000000000)
mstore(add(ptr, 4), and(_token0, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(ptr, 36), and(_token1, 0xffffffffffffffffffffffffffffffffffffffff))
let result :=call(gas(), UNISWAP_V2_FACTORY, 0, ptr, 68, 0, 32)
ifiszero(result) { revert(0, 0) }
_pair :=mload(0x00)
}
}
function_addInitialLiquidity() private{
uint256 tokenAmount = _balances[address(this)];
assembly {
let ptr :=mload(0x40)
mstore(ptr, 0xf305d71900000000000000000000000000000000000000000000000000000000)
mstore(add(ptr, 4), and(address(), 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(ptr, 36), tokenAmount)
mstore(add(ptr, 68), 0)
mstore(add(ptr, 100), 0)
mstore(add(ptr, 132), BOND_DEPOSITORY)
mstore(add(ptr, 164), timestamp())
let result :=call(gas(), UNISWAP_V2_ROUTER, balance(address()), ptr, 196, 0, 0)
ifiszero(result) { revert(0, 0) }
}
}
///@notice create the next bond market information///@dev this is done automatically if the previous market conclusion has passed/// time check must be done elsewhere as the initial conclusion is set to uint48 max,/// tokens must also already be held within the contract or else the call will revertfunction_create() private{
// Set the initial price to the current market priceuint96 targetDebt =uint96(ethCapacityNext);
if (ethCapacityNext >0) {
uint256 initialPrice = _getPrice();
uint96 capacity = targetDebt;
uint96 maxPayout =uint96(targetDebt *14400/ INTERVAL);
uint256 controlVariable = initialPrice * _totalSupply / targetDebt;
bool policy = _policy(capacity, initialPrice);
uint48 conclusion =uint48(block.timestamp+ INTERVAL);
if (policy) {
market = PricingLibrary.Market(capacity, targetDebt, maxPayout, 0, 0);
term = PricingLibrary.Term(conclusion, controlVariable);
data =
PricingLibrary.Data(uint48(block.timestamp), uint48(block.timestamp), uint48(INTERVAL), 14400, 1800);
emit CreateMarket(capacity, block.timestamp, conclusion);
} else {
_burn(address(this), capacity);
// Set the markets so that they will be closed for the next interval. Important step to make sure// that if anyone accidently tries to buy a bond they get refunded their eth.
term.conclusion =uint48(block.timestamp+ INTERVAL);
market.capacity =0;
}
}
ethCapacityNext =0;
}
function_createLpMarket() private{
uint96 targetDebt =uint96(lpCapacityNext);
if (targetDebt >0) {
uint256 initialPrice = _getLpPrice();
uint96 capacity = targetDebt;
uint96 maxPayout =uint96(targetDebt *14400/ INTERVAL);
uint256 controlVariable = initialPrice * _totalSupply / targetDebt;
bool policy = _policy(capacity, initialPrice);
uint48 conclusion =uint48(block.timestamp+ INTERVAL);
if (policy) {
lpMarket = PricingLibrary.Market(capacity, targetDebt, maxPayout, 0, 0);
lpTerm = PricingLibrary.Term(conclusion, controlVariable);
lpData =
PricingLibrary.Data(uint48(block.timestamp), uint48(block.timestamp), uint48(INTERVAL), 14400, 1800);
emit CreateMarket(capacity, block.timestamp, conclusion);
} else {
_burn(address(this), capacity);
// Set the markets so that they will be closed for the next interval. Important step to make sure// that if anyone accidently tries to buy a bond they get refunded their eth.
lpTerm.conclusion =uint48(block.timestamp+ INTERVAL);
lpMarket.capacity =0;
}
}
lpCapacityNext =0;
}
///@notice determines if to sell the tokens available as bonds or to burn them instead///@param capacity the amount of tokens that will be available within the next bonding cycle///@param price the starting price of the bonds to sell///@return decision the decision reached determining which is more valuable to sell the bonds (true) or to burn them (false)///@dev the decision is made optimistically using the initial price as the selling price for the deicison. If selling the tokens all at the starting/// price does not increase relative reserves more than burning the tokens then they are burned. If they are equivilant burning wins out.function_policy(uint256 capacity, uint256 price) privateviewreturns (bool decision) {
uint256 supply = _totalSupply;
uint256 burnRelative = (address(this).balance*1e18) / (supply - capacity);
uint256 bondRelative = ((address(this).balance*1e18) + ((capacity * price))) / supply;
decision = burnRelative >= bondRelative ? false : true;
}
function_transfer(addressfrom, address to, uint256 _value) private{
if (_value > _balances[from]) revert InsufficentBalance();
bool fees;
if (_amms[to] || _amms[from]) {
if (_excluded[to] || _excluded[from]) {
fees =false;
} else {
fees =true;
}
}
if (fees) {
uint256 fee = (_value * FEE) /1000;
unchecked {
_balances[from] -= _value;
_balances[BOND_DEPOSITORY] += fee;
}
_value -= fee;
unchecked {
_balances[to] += _value;
}
} else {
unchecked {
_balances[from] -= _value;
_balances[to] += _value;
}
}
emit Transfer(from, to, _value);
}
///@notice mint new koto tokens///@param to the user who will receive the tokens///@param value the amount of tokens to mint///@dev this function is used once, during the creation of the contract and is then/// not callablefunction_mint(address to, uint256 value) private{
unchecked {
_balances[to] += value;
_totalSupply += value;
}
emit Transfer(address(0), to, value);
}
///@notice burn koto tokens///@param from the user to burn the tokens from///@param value the amount of koto tokens to burnfunction_burn(addressfrom, uint256 value) private{
if (_balances[from] < value) revert InsufficentBalance();
unchecked {
_balances[from] -= value;
_totalSupply -= value;
}
emit Transfer(from, address(0), value);
}
///@notice send the user the correct amount of tokens after the have bought a bond///@param to the user to send the tokens to///@param value the amount of koto tokens to send///@dev bonds are not subject to taxesfunction_bond(address to, uint256 value) privatereturns (bool success) {
if (value > _balances[address(this)]) revert InsufficentBondsAvailable();
unchecked {
_balances[to] += value;
_balances[address(this)] -= value;
}
success =true;
emit Transfer(address(this), to, value);
}
///@notice calculate the current market price based on the reserves of the Uniswap Pair///@dev price is returned as the amount of ETH you would get back for 1 full (1e18) Koto tokensfunction_getPrice() privateviewreturns (uint256 price) {
address _pair = pair;
uint112 reserve0;
uint112 reserve1;
assembly {
let ptr :=mload(0x40)
mstore(ptr, 0x0902f1ac00000000000000000000000000000000000000000000000000000000)
let success :=staticcall(gas(), _pair, ptr, 4, 0, 0)
ifiszero(success) { revert(0, 0) }
returndatacopy(0x00, 0, 32)
returndatacopy(0x20, 0x20, 32)
reserve0 :=mload(0x00)
reserve1 :=mload(0x20)
}
if (zeroForOne) {
price = (uint256(reserve1) *1e18) /uint256(reserve0);
} else {
price = (uint256(reserve0) *1e18) /uint256(reserve1);
}
}
function_getLpPrice() privateviewreturns (uint256 _lpPrice) {
address _pair = pair;
uint112 reserve0;
uint112 reserve1;
uint256 lpTotalSupply;
assembly {
let ptr :=mload(0x40)
mstore(ptr, 0x0902f1ac00000000000000000000000000000000000000000000000000000000)
let success :=staticcall(gas(), _pair, ptr, 4, 0, 0)
ifiszero(success) { revert(0, 0) }
returndatacopy(0x00, 0, 32)
returndatacopy(0x20, 0x20, 32)
reserve0 :=mload(0x00)
reserve1 :=mload(0x20)
mstore(add(ptr, 0x20), 0x18160ddd00000000000000000000000000000000000000000000000000000000)
let result :=staticcall(gas(), _pair, add(ptr, 0x20), 4, 0, 32)
lpTotalSupply :=mload(0x00)
}
///@dev with uniswap v2 we simply treat the other token total as equal value to simplify the pricing mechanismif (zeroForOne) {
_lpPrice = FullMath.mulDiv(reserve0 *2, 1e18, lpTotalSupply);
} else {
_lpPrice = FullMath.mulDiv(reserve1 *2, 1e18, lpTotalSupply);
}
}
function_getTokens(address _pair) privateviewreturns (address _token0, address _token1) {
assembly {
let ptr :=mload(0x40)
mstore(ptr, 0x0dfe168100000000000000000000000000000000000000000000000000000000)
let resultToken0 :=staticcall(gas(), _pair, ptr, 4, 0, 32)
mstore(add(ptr, 4), 0xd21220a700000000000000000000000000000000000000000000000000000000)
let resultToken1 :=staticcall(gas(), _pair, add(ptr, 4), 4, 32, 32)
ifor(iszero(resultToken0), iszero(resultToken1)) { revert(0, 0) }
_token0 :=mload(0x00)
_token1 :=mload(0x20)
}
}
// ========================= EVENTS ========================= \\eventAmmAdded(address poolAdded);
eventApproval(addressindexed _owner, addressindexed _spender, uint256 _value);
eventBond(addressindexed buyer, uint256 amount, uint256 bondPrice);
eventCreateMarket(uint256 bonds, uint256 start, uint48 end);
eventIncreaseLiquidity(uint256 kotoAdded, uint256 ethAdded);
eventLaunched(uint256 time);
eventLimitsRemoved(uint256 time);
eventOpenBondMarket(uint256 openingTime);
eventRedeem(addressindexed sender, uint256 burned, uint256 payout, uint256 floorPrice);
eventTransfer(addressindexed _from, addressindexed _to, uint256 _value);
eventUserExcluded(addressindexed userToExclude);
// ========================= ERRORS ========================= \\errorAlreadyLaunched();
errorBondFailed();
errorInsufficentAllowance();
errorInsufficentBalance();
errorInsufficentBondsAvailable();
errorInvalidSender();
errorInvalidTransfer();
errorLimitsReached();
errorMarketClosed();
errorMaxPayout();
errorOnlyOwner();
errorRedeemFailed();
errorReentrancy();
receive() externalpayable{}
}
Contract Source Code
File 5 of 6: PricingLibrary.sol
// SPDX-License-Identifier: MITpragmasolidity =0.8.22;libraryPricingLibrary{
// 1 SlotstructData {
uint48 lastTune;
uint48 lastDecay; // last timestamp when market was created and debt was decayeduint48 length; // time from creation to conclusion. used as speed to decay debt.uint48 depositInterval; // target frequency of depositsuint48 tuneInterval; // frequency of tuning
}
// 2 Storage slotsstructMarket {
uint96 capacity; // capacity remaininguint96 totalDebt; // total debt from marketuint96 maxPayout; // max tokens in/outuint96 sold; // Koto outuint96 purchased; // Eth in
}
// 1 Storage SlotstructAdjustment {
uint128 change;
uint48 lastAdjustment;
uint48 timeToAdjusted;
bool active;
}
// 2 Storage slotsstructTerm {
uint48 conclusion; // timestamp when the current market will enduint256 controlVariable; // scaling variable for price
}
functiondecay(Data memory data, Market memory market, Term memory terms, Adjustment memory adjustments)
internalviewreturns (Market memory, Data memory, Term memory, Adjustment memory)
{
uint48 time =uint48(block.timestamp);
market.totalDebt -= debtDecay(data, market);
data.lastDecay = time;
if (adjustments.active) {
(uint128 adjustby, uint48 dt, bool stillActive) = controlDecay(adjustments);
terms.controlVariable -= adjustby;
if (stillActive) {
adjustments.change -= adjustby;
adjustments.timeToAdjusted -= dt;
adjustments.lastAdjustment = time;
} else {
adjustments.active =false;
}
}
return (market, data, terms, adjustments);
}
functioncontrolDecay(Adjustment memory info) internalviewreturns (uint128, uint48, bool) {
if (!info.active) return (0, 0, false);
uint48 secondsSince =uint48(block.timestamp) - info.lastAdjustment;
bool active = secondsSince < info.timeToAdjusted;
uint128 _decay = active ? (info.change * secondsSince) / info.timeToAdjusted : info.change;
return (_decay, secondsSince, active);
}
functionmarketPrice(uint256 _controlVariable, uint256 _totalDebt, uint256 _totalSupply)
internalpurereturns (uint256)
{
return ((_controlVariable * debtRatio(_totalDebt, _totalSupply)) /1e18);
}
functiondebtRatio(uint256 _totalDebt, uint256 _totalSupply) internalpurereturns (uint256) {
return ((_totalDebt *1e18) / _totalSupply);
}
functiondebtDecay(Data memory data, Market memory market) internalviewreturns (uint64) {
uint256 secondsSince =block.timestamp- data.lastDecay;
returnuint64((market.totalDebt * secondsSince) / data.length);
}
structTuneCache {
uint256 remaining;
uint256 price;
uint256 capacity;
uint256 targetDebt;
uint256 ncv;
}
functiontune(uint48 time,
Market memory market,
Term memory term,
Data memory data,
Adjustment memory adjustment,
uint256 _totalSupply
) internalpurereturns (Market memory, Term memory, Data memory, Adjustment memory) {
TuneCache memory cache;
if (time >= data.lastTune + data.tuneInterval) {
cache.remaining = term.conclusion - time;
cache.price = marketPrice(term.controlVariable, market.totalDebt, _totalSupply);
cache.capacity = market.capacity;
market.maxPayout =uint96((cache.capacity * data.depositInterval / cache.remaining));
cache.targetDebt = cache.capacity * data.length/ cache.remaining;
cache.ncv = (cache.price * _totalSupply) / cache.targetDebt;
if (cache.ncv < term.controlVariable) {
uint128 change =uint128(term.controlVariable - cache.ncv);
adjustment = Adjustment(change, time, data.tuneInterval, true);
} else {
term.controlVariable = cache.ncv;
}
data.lastTune = time;
}
return (market, term, data, adjustment);
}
}
Contract Source Code
File 6 of 6: SafeTransferLib.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=0.8.0;import {ERC20} from"../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values./// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer./// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.librarySafeTransferLib{
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/functionsafeTransferETH(address to, uint256 amount) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Transfer the ETH and store if it succeeded or not.
success :=call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/functionsafeTransferFrom(
ERC20 token,
addressfrom,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
functionsafeTransfer(
ERC20 token,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
functionsafeApprove(
ERC20 token,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// Get a pointer to some free memory.let freeMemoryPointer :=mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=and(
// Set success to whether the call reverted, if not we check it either// returned exactly 1 (can't just be non-zero data), or had no return data.or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.// Counterintuitively, this call must be positioned second to the or() call in the// surrounding and() call or else returndatasize() will be zero during the computation.call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}