// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Auth.sol)
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
abstract contract Auth {
event OwnerUpdated(address indexed user, address indexed newOwner);
event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
address public owner;
Authority public authority;
constructor(address _owner, Authority _authority) {
owner = _owner;
authority = _authority;
emit OwnerUpdated(msg.sender, _owner);
emit AuthorityUpdated(msg.sender, _authority);
}
modifier requiresAuth() {
require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED");
_;
}
function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.
// Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
// aware that this makes protected functions uncallable even to the owner if the authority is out of order.
return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner;
}
function setAuthority(Authority newAuthority) public virtual {
// We check if the caller is the owner first because we want to ensure they can
// always swap out the authority even if it's reverting or using up a lot of gas.
require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig));
authority = newAuthority;
emit AuthorityUpdated(msg.sender, newAuthority);
}
function setOwner(address newOwner) public virtual requiresAuth {
owner = newOwner;
emit OwnerUpdated(msg.sender, newOwner);
}
}
/// @notice A generic interface for a contract which provides authorization data to an Auth instance.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Auth.sol)
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
interface Authority {
function canCall(
address user,
address target,
bytes4 functionSig
) external view returns (bool);
}
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.15;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
import {Auth, Authority} from "solmate/auth/Auth.sol";
import {IBondTeller} from "../interfaces/IBondTeller.sol";
import {IBondCallback} from "../interfaces/IBondCallback.sol";
import {IBondAggregator} from "../interfaces/IBondAggregator.sol";
import {IBondAuctioneer} from "../interfaces/IBondAuctioneer.sol";
import {TransferHelper} from "../lib/TransferHelper.sol";
import {FullMath} from "../lib/FullMath.sol";
/// @title Bond Teller
/// @notice Bond Teller Base Contract
/// @dev Bond Protocol is a permissionless system to create Olympus-style bond markets
/// for any token pair. The markets do not require maintenance and will manage
/// bond prices based on activity. Bond issuers create BondMarkets that pay out
/// a Payout Token in exchange for deposited Quote Tokens. Users can purchase
/// future-dated Payout Tokens with Quote Tokens at the current market price and
/// receive Bond Tokens to represent their position while their bond vests.
/// Once the Bond Tokens vest, they can redeem it for the Quote Tokens.
///
/// @dev The Teller contract handles all interactions with end users and manages tokens
/// issued to represent bond positions. Users purchase bonds by depositing Quote Tokens
/// and receive a Bond Token (token type is implementation-specific) that represents
/// their payout and the designated expiry. Once a bond vests, Investors can redeem their
/// Bond Tokens for the underlying Payout Token. A Teller requires one or more Auctioneer
/// contracts to be deployed to provide markets for users to purchase bonds from.
///
/// @author Oighty, Zeus, Potted Meat, indigo
abstract contract BondBaseTeller is IBondTeller, Auth, ReentrancyGuard {
using TransferHelper for ERC20;
using FullMath for uint256;
/* ========== ERRORS ========== */
error Teller_InvalidCallback();
error Teller_TokenNotMatured(uint48 maturesOn);
error Teller_NotAuthorized();
error Teller_TokenDoesNotExist(ERC20 underlying, uint48 expiry);
error Teller_UnsupportedToken();
error Teller_InvalidParams();
/* ========== EVENTS ========== */
event Bonded(uint256 indexed id, address indexed referrer, uint256 amount, uint256 payout);
/* ========== STATE VARIABLES ========== */
/// @notice Fee paid to a front end operator in basis points (3 decimals). Set by the referrer, must be less than or equal to 5% (5e3).
/// @dev There are some situations where the fees may round down to zero if quantity of baseToken
/// is < 1e5 wei (can happen with big price differences on small decimal tokens). This is purely
/// a theoretical edge case, as the bond amount would not be practical.
mapping(address => uint48) public referrerFees;
/// @notice Fee paid to protocol in basis points (3 decimal places).
uint48 public protocolFee;
/// @notice 'Create' function fee discount in basis points (3 decimal places). Amount standard fee is reduced by for partners who just want to use the 'create' function to issue bond tokens.
uint48 public createFeeDiscount;
uint48 public constant FEE_DECIMALS = 1e5; // one percent equals 1000.
/// @notice Fees earned by an address, by token
mapping(address => mapping(ERC20 => uint256)) public rewards;
// Address the protocol receives fees at
address internal immutable _protocol;
// BondAggregator contract with utility functions
IBondAggregator internal immutable _aggregator;
constructor(
address protocol_,
IBondAggregator aggregator_,
address guardian_,
Authority authority_
) Auth(guardian_, authority_) {
_protocol = protocol_;
_aggregator = aggregator_;
// Explicitly setting these values to zero to document
protocolFee = 0;
createFeeDiscount = 0;
}
/// @inheritdoc IBondTeller
function setReferrerFee(uint48 fee_) external override nonReentrant {
if (fee_ > 5e3) revert Teller_InvalidParams();
referrerFees[msg.sender] = fee_;
}
/// @inheritdoc IBondTeller
function setProtocolFee(uint48 fee_) external override requiresAuth {
if (fee_ > 5e3) revert Teller_InvalidParams();
protocolFee = fee_;
}
/// @inheritdoc IBondTeller
function setCreateFeeDiscount(uint48 discount_) external override requiresAuth {
if (discount_ > protocolFee) revert Teller_InvalidParams();
createFeeDiscount = discount_;
}
/// @inheritdoc IBondTeller
function claimFees(ERC20[] memory tokens_, address to_) external override nonReentrant {
uint256 len = tokens_.length;
for (uint256 i; i < len; ++i) {
ERC20 token = tokens_[i];
uint256 send = rewards[msg.sender][token];
if (send != 0) {
rewards[msg.sender][token] = 0;
token.safeTransfer(to_, send);
}
}
}
/// @inheritdoc IBondTeller
function getFee(address referrer_) external view returns (uint48) {
return protocolFee + referrerFees[referrer_];
}
/* ========== USER FUNCTIONS ========== */
/// @inheritdoc IBondTeller
function purchase(
address recipient_,
address referrer_,
uint256 id_,
uint256 amount_,
uint256 minAmountOut_
) external virtual nonReentrant returns (uint256, uint48) {
ERC20 payoutToken;
ERC20 quoteToken;
uint48 vesting;
uint256 payout;
// Calculate fees for purchase
// 1. Calculate referrer fee
// 2. Calculate protocol fee as the total expected fee amount minus the referrer fee
// to avoid issues with rounding from separate fee calculations
uint256 toReferrer = amount_.mulDiv(referrerFees[referrer_], FEE_DECIMALS);
uint256 toProtocol = amount_.mulDiv(protocolFee + referrerFees[referrer_], FEE_DECIMALS) -
toReferrer;
{
IBondAuctioneer auctioneer = _aggregator.getAuctioneer(id_);
address owner;
(owner, , payoutToken, quoteToken, vesting, ) = auctioneer.getMarketInfoForPurchase(
id_
);
// Auctioneer handles bond pricing, capacity, and duration
uint256 amountLessFee = amount_ - toReferrer - toProtocol;
payout = auctioneer.purchaseBond(id_, amountLessFee, minAmountOut_);
}
// Allocate fees to protocol and referrer
rewards[referrer_][quoteToken] += toReferrer;
rewards[_protocol][quoteToken] += toProtocol;
// Transfer quote tokens from sender and ensure enough payout tokens are available
_handleTransfers(id_, amount_, payout, toReferrer + toProtocol);
// Handle payout to user (either transfer tokens if instant swap or issue bond token)
uint48 expiry = _handlePayout(recipient_, payout, payoutToken, vesting);
emit Bonded(id_, referrer_, amount_, payout);
return (payout, expiry);
}
/// @notice Handles transfer of funds from user and market owner/callback
function _handleTransfers(
uint256 id_,
uint256 amount_,
uint256 payout_,
uint256 feePaid_
) internal {
// Get info from auctioneer
(address owner, address callbackAddr, ERC20 payoutToken, ERC20 quoteToken, , ) = _aggregator
.getAuctioneer(id_)
.getMarketInfoForPurchase(id_);
// Calculate amount net of fees
uint256 amountLessFee = amount_ - feePaid_;
// Have to transfer to teller first since fee is in quote token
// Check balance before and after to ensure full amount received, revert if not
// Handles edge cases like fee-on-transfer tokens (which are not supported)
uint256 quoteBalance = quoteToken.balanceOf(address(this));
quoteToken.safeTransferFrom(msg.sender, address(this), amount_);
if (quoteToken.balanceOf(address(this)) < quoteBalance + amount_)
revert Teller_UnsupportedToken();
// If callback address supplied, transfer tokens from teller to callback, then execute callback function,
// and ensure proper amount of tokens transferred in.
if (callbackAddr != address(0)) {
// Send quote token to callback (transferred in first to allow use during callback)
quoteToken.safeTransfer(callbackAddr, amountLessFee);
// Call the callback function to receive payout tokens for payout
uint256 payoutBalance = payoutToken.balanceOf(address(this));
IBondCallback(callbackAddr).callback(id_, amountLessFee, payout_);
// Check to ensure that the callback sent the requested amount of payout tokens back to the teller
if (payoutToken.balanceOf(address(this)) < (payoutBalance + payout_))
revert Teller_InvalidCallback();
} else {
// If no callback is provided, transfer tokens from market owner to this contract
// for payout.
// Check balance before and after to ensure full amount received, revert if not
// Handles edge cases like fee-on-transfer tokens (which are not supported)
uint256 payoutBalance = payoutToken.balanceOf(address(this));
payoutToken.safeTransferFrom(owner, address(this), payout_);
if (payoutToken.balanceOf(address(this)) < (payoutBalance + payout_))
revert Teller_UnsupportedToken();
quoteToken.safeTransfer(owner, amountLessFee);
}
}
/// @notice Handle payout to recipient
/// @dev Implementation-agnostic. Must be implemented in contracts that
/// extend this base since it is called by purchase.
/// @param recipient_ Address to receive payout
/// @param payout_ Amount of payoutToken to be paid
/// @param underlying_ Token to be paid out
/// @param vesting_ Time parameter for when the payout is available, could be a
/// timestamp or duration depending on the implementation
/// @return expiry Timestamp when the payout will vest
function _handlePayout(
address recipient_,
uint256 payout_,
ERC20 underlying_,
uint48 vesting_
) internal virtual returns (uint48 expiry);
/// @notice Derive name and symbol of token for market
/// @param underlying_ Underlying token to be paid out when the Bond Token vests
/// @param expiry_ Timestamp that the Bond Token vests at
/// @return name Bond token name, format is "Token YYYY-MM-DD"
/// @return symbol Bond token symbol, format is "TKN-YYYYMMDD"
function _getNameAndSymbol(ERC20 underlying_, uint256 expiry_)
internal
view
returns (string memory name, string memory symbol)
{
// Convert a number of days into a human-readable date, courtesy of BokkyPooBah.
// Source: https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary/blob/master/contracts/BokkyPooBahsDateTimeLibrary.sol
uint256 year;
uint256 month;
uint256 day;
{
int256 __days = int256(expiry_ / 1 days);
int256 num1 = __days + 68569 + 2440588; // 2440588 = OFFSET19700101
int256 num2 = (4 * num1) / 146097;
num1 = num1 - (146097 * num2 + 3) / 4;
int256 _year = (4000 * (num1 + 1)) / 1461001;
num1 = num1 - (1461 * _year) / 4 + 31;
int256 _month = (80 * num1) / 2447;
int256 _day = num1 - (2447 * _month) / 80;
num1 = _month / 11;
_month = _month + 2 - 12 * num1;
_year = 100 * (num2 - 49) + _year + num1;
year = uint256(_year);
month = uint256(_month);
day = uint256(_day);
}
string memory yearStr = _uint2str(year % 10000);
string memory monthStr = month < 10
? string(abi.encodePacked("0", _uint2str(month)))
: _uint2str(month);
string memory dayStr = day < 10
? string(abi.encodePacked("0", _uint2str(day)))
: _uint2str(day);
// Construct name/symbol strings.
name = string(
abi.encodePacked(underlying_.name(), " ", yearStr, "-", monthStr, "-", dayStr)
);
symbol = string(abi.encodePacked(underlying_.symbol(), "-", yearStr, monthStr, dayStr));
}
// Some fancy math to convert a uint into a string, courtesy of Provable Things.
// Updated to work with solc 0.8.0.
// https://github.com/provable-things/ethereum-api/blob/master/provableAPI_0.6.sol
function _uint2str(uint256 _i) internal pure returns (string memory) {
if (_i == 0) {
return "0";
}
uint256 j = _i;
uint256 len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint256 k = len;
while (_i != 0) {
k = k - 1;
uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
bytes1 b1 = bytes1(temp);
bstr[k] = b1;
_i /= 10;
}
return string(bstr);
}
}
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.15;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {BondBaseTeller, IBondAggregator, Authority} from "./bases/BondBaseTeller.sol";
import {IBondFixedTermTeller} from "./interfaces/IBondFixedTermTeller.sol";
import {TransferHelper} from "./lib/TransferHelper.sol";
import {FullMath} from "./lib/FullMath.sol";
import {ERC1155} from "./lib/ERC1155.sol";
/// @title Bond Fixed Term Teller
/// @notice Bond Fixed Term Teller Contract
/// @dev Bond Protocol is a permissionless system to create Olympus-style bond markets
/// for any token pair. The markets do not require maintenance and will manage
/// bond prices based on activity. Bond issuers create BondMarkets that pay out
/// a Payout Token in exchange for deposited Quote Tokens. Users can purchase
/// future-dated Payout Tokens with Quote Tokens at the current market price and
/// receive Bond Tokens to represent their position while their bond vests.
/// Once the Bond Tokens vest, they can redeem it for the Quote Tokens.
///
/// @dev The Bond Fixed Term Teller is an implementation of the
/// Bond Base Teller contract specific to handling user bond transactions
/// and tokenizing bond markets where purchases vest in a fixed amount of time
/// (rounded to the day) as ERC1155 tokens.
///
/// @author Oighty, Zeus, Potted Meat, indigo
contract BondFixedTermTeller is BondBaseTeller, IBondFixedTermTeller, ERC1155 {
using TransferHelper for ERC20;
using FullMath for uint256;
/* ========== EVENTS ========== */
event ERC1155BondTokenCreated(uint256 tokenId, ERC20 indexed underlying, uint48 indexed expiry);
/* ========== STATE VARIABLES ========== */
mapping(uint256 => TokenMetadata) public tokenMetadata; // metadata for bond tokens
/* ========== CONSTRUCTOR ========== */
constructor(
address protocol_,
IBondAggregator aggregator_,
address guardian_,
Authority authority_
) BondBaseTeller(protocol_, aggregator_, guardian_, authority_) {}
/* ========== PURCHASE ========== */
/// @notice Handle payout to recipient
/// @param recipient_ Address to receive payout
/// @param payout_ Amount of payoutToken to be paid
/// @param payoutToken_ Token to be paid out
/// @param vesting_ Amount of time to vest from current timestamp
/// @return expiry Timestamp when the payout will vest
function _handlePayout(
address recipient_,
uint256 payout_,
ERC20 payoutToken_,
uint48 vesting_
) internal override returns (uint48 expiry) {
// If there is no vesting time, the deposit is treated as an instant swap.
// otherwise, deposit info is stored and payout is available at a future timestamp.
// instant swap is denoted by expiry == 0.
//
// bonds mature with a cliff at a set timestamp
// prior to the expiry timestamp, no payout tokens are accessible to the user
// after the expiry timestamp, the entire payout can be redeemed
//
// fixed-term bonds mature in a set amount of time from deposit
// i.e. term = 1 week. when alice deposits on day 1, her bond
// expires on day 8. when bob deposits on day 2, his bond expires day 9.
if (vesting_ != 0) {
// Normalizing fixed term vesting timestamps to the same time each day
expiry = ((vesting_ + uint48(block.timestamp)) / uint48(1 days)) * uint48(1 days);
// Fixed-term user payout information is handled in BondTeller.
// Teller mints ERC-1155 bond tokens for user.
uint256 tokenId = getTokenId(payoutToken_, expiry);
// Create new bond token if it doesn't exist yet
if (!tokenMetadata[tokenId].active) {
_deploy(tokenId, payoutToken_, expiry);
}
// Mint bond token to recipient
_mintToken(recipient_, tokenId, payout_);
} else {
// If no expiry, then transfer payout directly to user
payoutToken_.safeTransfer(recipient_, payout_);
}
}
/* ========== DEPOSIT/MINT ========== */
/// @inheritdoc IBondFixedTermTeller
function create(
ERC20 underlying_,
uint48 expiry_,
uint256 amount_
) external override nonReentrant returns (uint256, uint256) {
// Expiry is rounded to the nearest day at 0000 UTC (in seconds) since bond tokens
// are only unique to a day, not a specific timestamp.
uint48 expiry = uint48(expiry_ / 1 days) * 1 days;
// Revert if expiry is in the past
if (expiry < block.timestamp) revert Teller_InvalidParams();
uint256 tokenId = getTokenId(underlying_, expiry);
// Revert if no token exists, must call deploy first
if (!tokenMetadata[tokenId].active) revert Teller_TokenDoesNotExist(underlying_, expiry);
// Transfer in underlying
// Check that amount received is not less than amount expected
// Handles edge cases like fee-on-transfer tokens (which are not supported)
uint256 oldBalance = underlying_.balanceOf(address(this));
underlying_.safeTransferFrom(msg.sender, address(this), amount_);
if (underlying_.balanceOf(address(this)) < oldBalance + amount_)
revert Teller_UnsupportedToken();
// If fee is greater than the create discount, then calculate the fee and store it
// Otherwise, fee is zero.
if (protocolFee > createFeeDiscount) {
// Calculate fee amount
uint256 feeAmount = amount_.mulDiv(protocolFee - createFeeDiscount, FEE_DECIMALS);
rewards[_protocol][underlying_] += feeAmount;
// Mint new bond tokens
_mintToken(msg.sender, tokenId, amount_ - feeAmount);
return (tokenId, amount_ - feeAmount);
} else {
// Mint new bond tokens
_mintToken(msg.sender, tokenId, amount_);
return (tokenId, amount_);
}
}
/* ========== REDEEM ========== */
function _redeem(uint256 tokenId_, uint256 amount_) internal {
// Check that the tokenId is active
if (!tokenMetadata[tokenId_].active) revert Teller_InvalidParams();
// Cache token metadata
TokenMetadata memory meta = tokenMetadata[tokenId_];
// Check that the token has matured
if (block.timestamp < meta.expiry) revert Teller_TokenNotMatured(meta.expiry);
// Burn bond token and transfer underlying to sender
_burnToken(msg.sender, tokenId_, amount_);
meta.underlying.safeTransfer(msg.sender, amount_);
}
/// @inheritdoc IBondFixedTermTeller
function redeem(uint256 tokenId_, uint256 amount_) public override nonReentrant {
_redeem(tokenId_, amount_);
}
/// @inheritdoc IBondFixedTermTeller
function batchRedeem(uint256[] calldata tokenIds_, uint256[] calldata amounts_)
external
override
nonReentrant
{
uint256 len = tokenIds_.length;
if (len != amounts_.length) revert Teller_InvalidParams();
for (uint256 i; i < len; ++i) {
_redeem(tokenIds_[i], amounts_[i]);
}
}
/* ========== TOKENIZATION ========== */
/// @inheritdoc IBondFixedTermTeller
function deploy(ERC20 underlying_, uint48 expiry_)
external
override
nonReentrant
returns (uint256)
{
uint256 tokenId = getTokenId(underlying_, expiry_);
// Only creates token if it does not exist
if (!tokenMetadata[tokenId].active) {
_deploy(tokenId, underlying_, expiry_);
}
return tokenId;
}
/// @notice "Deploy" a new ERC1155 bond token and stores its ID
/// @dev ERC1155 tokens used for fixed term bonds
/// @param tokenId_ Calculated ID of new bond token (from getTokenId)
/// @param underlying_ Underlying token to be paid out when the bond token vests
/// @param expiry_ Timestamp that the token will vest at, will be rounded to the nearest day
function _deploy(
uint256 tokenId_,
ERC20 underlying_,
uint48 expiry_
) internal {
// Expiry is rounded to the nearest day at 0000 UTC (in seconds) since bond tokens
// are only unique to a day, not a specific timestamp.
uint48 expiry = uint48(expiry_ / 1 days) * 1 days;
// Revert if expiry is in the past
if (uint256(expiry) < block.timestamp) revert Teller_InvalidParams();
// Store token metadata
tokenMetadata[tokenId_] = TokenMetadata(
true,
underlying_,
uint8(underlying_.decimals()),
expiry,
0
);
emit ERC1155BondTokenCreated(tokenId_, underlying_, expiry);
}
/// @notice Mint bond token and update supply
/// @param to_ Address to mint tokens to
/// @param tokenId_ ID of bond token to mint
/// @param amount_ Amount of bond tokens to mint
function _mintToken(
address to_,
uint256 tokenId_,
uint256 amount_
) internal {
tokenMetadata[tokenId_].supply += amount_;
_mint(to_, tokenId_, amount_, bytes(""));
}
/// @notice Burn bond token and update supply
/// @param from_ Address to burn tokens from
/// @param tokenId_ ID of bond token to burn
/// @param amount_ Amount of bond token to burn
function _burnToken(
address from_,
uint256 tokenId_,
uint256 amount_
) internal {
tokenMetadata[tokenId_].supply -= amount_;
_burn(from_, tokenId_, amount_);
}
/* ========== TOKEN NAMING ========== */
/// @inheritdoc IBondFixedTermTeller
function getTokenId(ERC20 underlying_, uint48 expiry_) public pure override returns (uint256) {
// Expiry is divided by 1 day (in seconds) since bond tokens are only unique
// to a day, not a specific timestamp.
uint256 tokenId = uint256(
keccak256(abi.encodePacked(underlying_, expiry_ / uint48(1 days)))
);
return tokenId;
}
/// @inheritdoc IBondFixedTermTeller
function getTokenNameAndSymbol(uint256 tokenId_)
external
view
override
returns (string memory, string memory)
{
TokenMetadata memory meta = tokenMetadata[tokenId_];
(string memory name, string memory symbol) = _getNameAndSymbol(
meta.underlying,
meta.expiry
);
return (name, symbol);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Minimalist and gas efficient standard ERC1155 implementation.
/// @dev Removed `uri()` function. Unnecessary for our use case.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155 {
/*///////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event TransferSingle(
address indexed operator,
address indexed from,
address indexed to,
uint256 id,
uint256 amount
);
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] amounts
);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
// event URI(string value, uint256 indexed id);
/*///////////////////////////////////////////////////////////////
ERC1155 STORAGE
//////////////////////////////////////////////////////////////*/
mapping(address => mapping(uint256 => uint256)) public balanceOf;
mapping(address => mapping(address => bool)) public isApprovedForAll;
/*///////////////////////////////////////////////////////////////
ERC1155 LOGIC
//////////////////////////////////////////////////////////////*/
function setApprovalForAll(address operator, bool approved) public virtual {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) public virtual {
require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");
balanceOf[from][id] -= amount;
balanceOf[to][id] += amount;
emit TransferSingle(msg.sender, from, to, id, amount);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155Received(msg.sender, from, id, amount, data) ==
ERC1155TokenReceiver.onERC1155Received.selector,
"UNSAFE_RECIPIENT"
);
}
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public virtual {
uint256 idsLength = ids.length; // Saves MLOADs.
require(idsLength == amounts.length, "LENGTH_MISMATCH");
require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");
// Storing these outside the loop saves ~15 gas per iteration.
uint256 id;
uint256 amount;
for (uint256 i = 0; i < idsLength; ) {
id = ids[i];
amount = amounts[i];
balanceOf[from][id] -= amount;
balanceOf[to][id] += amount;
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit TransferBatch(msg.sender, from, to, ids, amounts);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155BatchReceived(
msg.sender,
from,
ids,
amounts,
data
) == ERC1155TokenReceiver.onERC1155BatchReceived.selector,
"UNSAFE_RECIPIENT"
);
}
function balanceOfBatch(address[] memory owners, uint256[] memory ids)
public
view
virtual
returns (uint256[] memory balances)
{
uint256 ownersLength = owners.length; // Saves MLOADs.
require(ownersLength == ids.length, "LENGTH_MISMATCH");
balances = new uint256[](ownersLength);
// Unchecked because the only math done is incrementing
// the array index counter which cannot possibly overflow.
unchecked {
for (uint256 i = 0; i < ownersLength; ++i) {
balances[i] = balanceOf[owners[i]][ids[i]];
}
}
}
/*///////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI
}
/*///////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal {
balanceOf[to][id] += amount;
emit TransferSingle(msg.sender, address(0), to, id, amount);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155Received(
msg.sender,
address(0),
id,
amount,
data
) == ERC1155TokenReceiver.onERC1155Received.selector,
"UNSAFE_RECIPIENT"
);
}
function _batchMint(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal {
uint256 idsLength = ids.length; // Saves MLOADs.
require(idsLength == amounts.length, "LENGTH_MISMATCH");
for (uint256 i = 0; i < idsLength; ) {
balanceOf[to][ids[i]] += amounts[i];
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit TransferBatch(msg.sender, address(0), to, ids, amounts);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155BatchReceived(
msg.sender,
address(0),
ids,
amounts,
data
) == ERC1155TokenReceiver.onERC1155BatchReceived.selector,
"UNSAFE_RECIPIENT"
);
}
function _batchBurn(
address from,
uint256[] memory ids,
uint256[] memory amounts
) internal {
uint256 idsLength = ids.length; // Saves MLOADs.
require(idsLength == amounts.length, "LENGTH_MISMATCH");
for (uint256 i = 0; i < idsLength; ) {
balanceOf[from][ids[i]] -= amounts[i];
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit TransferBatch(msg.sender, from, address(0), ids, amounts);
}
function _burn(
address from,
uint256 id,
uint256 amount
) internal {
balanceOf[from][id] -= amount;
emit TransferSingle(msg.sender, from, address(0), id, amount);
}
}
/// @notice A generic interface for a contract which properly accepts ERC1155 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol)
interface ERC1155TokenReceiver {
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 amount,
bytes calldata data
) external returns (bytes4);
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/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.
abstract contract ERC20 {
/*///////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*///////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*///////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*///////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
bytes32 public constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*///////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*///////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (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);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (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);
return true;
}
/*///////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
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) internal virtual {
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(address from, uint256 amount) internal virtual {
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);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=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 bits
library FullMath {
/// @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/muldiv
function mulDiv(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (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 + prod0
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
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 division
if (prod1 == 0) {
require(denominator > 0);
assembly {
result := div(prod0, denominator)
}
return result;
}
// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly {
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 = (type(uint256).max - denominator + 1) & denominator;
// Divide denominator by power of two
assembly {
denominator := div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of two
assembly {
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 one
assembly {
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**4
uint256 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 result
function mulDivUp(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
result = mulDiv(a, b, denominator);
unchecked {
if (mulmod(a, b, denominator) > 0) {
require(result < type(uint256).max);
result++;
}
}
}
}
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.0;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {IBondAuctioneer} from "../interfaces/IBondAuctioneer.sol";
import {IBondTeller} from "../interfaces/IBondTeller.sol";
interface IBondAggregator {
/// @notice Register a auctioneer with the aggregator
/// @notice Only Guardian
/// @param auctioneer_ Address of the Auctioneer to register
/// @dev A auctioneer must be registered with an aggregator to create markets
function registerAuctioneer(IBondAuctioneer auctioneer_) external;
/// @notice Register a new market with the aggregator
/// @notice Only registered depositories
/// @param payoutToken_ Token to be paid out by the market
/// @param quoteToken_ Token to be accepted by the market
/// @param marketId ID of the market being created
function registerMarket(ERC20 payoutToken_, ERC20 quoteToken_)
external
returns (uint256 marketId);
/// @notice Get the auctioneer for the provided market ID
/// @param id_ ID of Market
function getAuctioneer(uint256 id_) external view returns (IBondAuctioneer);
/// @notice Calculate current market price of payout token in quote tokens
/// @dev Accounts for debt and control variable decay since last deposit (vs _marketPrice())
/// @param id_ ID of market
/// @return Price for market (see the specific auctioneer for units)
//
// if price is below minimum price, minimum price is returned
// this is enforced on deposits by manipulating total debt (see _decay())
function marketPrice(uint256 id_) external view returns (uint256);
/// @notice Scale value to use when converting between quote token and payout token amounts with marketPrice()
/// @param id_ ID of market
/// @return Scaling factor for market in configured decimals
function marketScale(uint256 id_) external view returns (uint256);
/// @notice Payout due for amount of quote tokens
/// @dev Accounts for debt and control variable decay so it is up to date
/// @param amount_ Amount of quote tokens to spend
/// @param id_ ID of market
/// @param referrer_ Address of referrer, used to get fees to calculate accurate payout amount.
/// Inputting the zero address will take into account just the protocol fee.
/// @return amount of payout tokens to be paid
function payoutFor(
uint256 amount_,
uint256 id_,
address referrer_
) external view returns (uint256);
/// @notice Returns maximum amount of quote token accepted by the market
/// @param id_ ID of market
/// @param referrer_ Address of referrer, used to get fees to calculate accurate payout amount.
/// Inputting the zero address will take into account just the protocol fee.
function maxAmountAccepted(uint256 id_, address referrer_) external view returns (uint256);
/// @notice Does market send payout immediately
/// @param id_ Market ID to search for
function isInstantSwap(uint256 id_) external view returns (bool);
/// @notice Is a given market accepting deposits
/// @param id_ ID of market
function isLive(uint256 id_) external view returns (bool);
/// @notice Returns array of active market IDs within a range
/// @dev Should be used if length exceeds max to query entire array
function liveMarketsBetween(uint256 firstIndex_, uint256 lastIndex_)
external
view
returns (uint256[] memory);
/// @notice Returns an array of all active market IDs for a given quote token
/// @param token_ Address of token to query by
/// @param isPayout_ If true, search by payout token, else search for quote token
function liveMarketsFor(address token_, bool isPayout_)
external
view
returns (uint256[] memory);
/// @notice Returns an array of all active market IDs for a given owner
/// @param owner_ Address of owner to query by
/// @param firstIndex_ Market ID to start at
/// @param lastIndex_ Market ID to end at (non-inclusive)
function liveMarketsBy(
address owner_,
uint256 firstIndex_,
uint256 lastIndex_
) external view returns (uint256[] memory);
/// @notice Returns an array of all active market IDs for a given payout and quote token
/// @param payout_ Address of payout token
/// @param quote_ Address of quote token
function marketsFor(address payout_, address quote_) external view returns (uint256[] memory);
/// @notice Returns the market ID with the highest current payoutToken payout for depositing quoteToken
/// @param payout_ Address of payout token
/// @param quote_ Address of quote token
/// @param amountIn_ Amount of quote tokens to deposit
/// @param minAmountOut_ Minimum amount of payout tokens to receive as payout
/// @param maxExpiry_ Latest acceptable vesting timestamp for bond
/// Inputting the zero address will take into account just the protocol fee.
function findMarketFor(
address payout_,
address quote_,
uint256 amountIn_,
uint256 minAmountOut_,
uint256 maxExpiry_
) external view returns (uint256 id);
/// @notice Returns the Teller that services the market ID
function getTeller(uint256 id_) external view returns (IBondTeller);
/// @notice Returns current capacity of a market
function currentCapacity(uint256 id_) external view returns (uint256);
}
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.0;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {IBondTeller} from "../interfaces/IBondTeller.sol";
import {IBondAggregator} from "../interfaces/IBondAggregator.sol";
interface IBondAuctioneer {
/// @notice Creates a new bond market
/// @param params_ Configuration data needed for market creation, encoded in a bytes array
/// @dev See specific auctioneer implementations for details on encoding the parameters.
/// @return id ID of new bond market
function createMarket(bytes memory params_) external returns (uint256);
/// @notice Disable existing bond market
/// @notice Must be market owner
/// @param id_ ID of market to close
function closeMarket(uint256 id_) external;
/// @notice Exchange quote tokens for a bond in a specified market
/// @notice Must be teller
/// @param id_ ID of the Market the bond is being purchased from
/// @param amount_ Amount to deposit in exchange for bond (after fee has been deducted)
/// @param minAmountOut_ Minimum acceptable amount of bond to receive. Prevents frontrunning
/// @return payout Amount of payout token to be received from the bond
function purchaseBond(
uint256 id_,
uint256 amount_,
uint256 minAmountOut_
) external returns (uint256 payout);
/// @notice Set market intervals to different values than the defaults
/// @notice Must be market owner
/// @dev Changing the intervals could cause markets to behave in unexpected way
/// tuneInterval should be greater than tuneAdjustmentDelay
/// @param id_ Market ID
/// @param intervals_ Array of intervals (3)
/// 1. Tune interval - Frequency of tuning
/// 2. Tune adjustment delay - Time to implement downward tuning adjustments
/// 3. Debt decay interval - Interval over which debt should decay completely
function setIntervals(uint256 id_, uint32[3] calldata intervals_) external;
/// @notice Designate a new owner of a market
/// @notice Must be market owner
/// @dev Doesn't change permissions until newOwner calls pullOwnership
/// @param id_ Market ID
/// @param newOwner_ New address to give ownership to
function pushOwnership(uint256 id_, address newOwner_) external;
/// @notice Accept ownership of a market
/// @notice Must be market newOwner
/// @dev The existing owner must call pushOwnership prior to the newOwner calling this function
/// @param id_ Market ID
function pullOwnership(uint256 id_) external;
/// @notice Set the auctioneer defaults
/// @notice Must be policy
/// @param defaults_ Array of default values
/// 1. Tune interval - amount of time between tuning adjustments
/// 2. Tune adjustment delay - amount of time to apply downward tuning adjustments
/// 3. Minimum debt decay interval - minimum amount of time to let debt decay to zero
/// 4. Minimum deposit interval - minimum amount of time to wait between deposits
/// 5. Minimum market duration - minimum amount of time a market can be created for
/// 6. Minimum debt buffer - the minimum amount of debt over the initial debt to trigger a market shutdown
/// @dev The defaults set here are important to avoid edge cases in market behavior, e.g. a very short market reacts doesn't tune well
/// @dev Only applies to new markets that are created after the change
function setDefaults(uint32[6] memory defaults_) external;
/// @notice Change the status of the auctioneer to allow creation of new markets
/// @dev Setting to false and allowing active markets to end will sunset the auctioneer
/// @param status_ Allow market creation (true) : Disallow market creation (false)
function setAllowNewMarkets(bool status_) external;
/// @notice Change whether a market creator is allowed to use a callback address in their markets or not
/// @notice Must be guardian
/// @dev Callback is believed to be safe, but a whitelist is implemented to prevent abuse
/// @param creator_ Address of market creator
/// @param status_ Allow callback (true) : Disallow callback (false)
function setCallbackAuthStatus(address creator_, bool status_) external;
/* ========== VIEW FUNCTIONS ========== */
/// @notice Provides information for the Teller to execute purchases on a Market
/// @param id_ Market ID
/// @return owner Address of the market owner (tokens transferred from this address if no callback)
/// @return callbackAddr Address of the callback contract to get tokens for payouts
/// @return payoutToken Payout Token (token paid out) for the Market
/// @return quoteToken Quote Token (token received) for the Market
/// @return vesting Timestamp or duration for vesting, implementation-dependent
/// @return maxPayout Maximum amount of payout tokens you can purchase in one transaction
function getMarketInfoForPurchase(uint256 id_)
external
view
returns (
address owner,
address callbackAddr,
ERC20 payoutToken,
ERC20 quoteToken,
uint48 vesting,
uint256 maxPayout
);
/// @notice Calculate current market price of payout token in quote tokens
/// @param id_ ID of market
/// @return Price for market in configured decimals
//
// if price is below minimum price, minimum price is returned
function marketPrice(uint256 id_) external view returns (uint256);
/// @notice Scale value to use when converting between quote token and payout token amounts with marketPrice()
/// @param id_ ID of market
/// @return Scaling factor for market in configured decimals
function marketScale(uint256 id_) external view returns (uint256);
/// @notice Payout due for amount of quote tokens
/// @dev Accounts for debt and control variable decay so it is up to date
/// @param amount_ Amount of quote tokens to spend
/// @param id_ ID of market
/// @param referrer_ Address of referrer, used to get fees to calculate accurate payout amount.
/// Inputting the zero address will take into account just the protocol fee.
/// @return amount of payout tokens to be paid
function payoutFor(
uint256 amount_,
uint256 id_,
address referrer_
) external view returns (uint256);
/// @notice Returns maximum amount of quote token accepted by the market
/// @param id_ ID of market
/// @param referrer_ Address of referrer, used to get fees to calculate accurate payout amount.
/// Inputting the zero address will take into account just the protocol fee.
function maxAmountAccepted(uint256 id_, address referrer_) external view returns (uint256);
/// @notice Does market send payout immediately
/// @param id_ Market ID to search for
function isInstantSwap(uint256 id_) external view returns (bool);
/// @notice Is a given market accepting deposits
/// @param id_ ID of market
function isLive(uint256 id_) external view returns (bool);
/// @notice Returns the address of the market owner
/// @param id_ ID of market
function ownerOf(uint256 id_) external view returns (address);
/// @notice Returns the Teller that services the Auctioneer
function getTeller() external view returns (IBondTeller);
/// @notice Returns the Aggregator that services the Auctioneer
function getAggregator() external view returns (IBondAggregator);
/// @notice Returns current capacity of a market
function currentCapacity(uint256 id_) external view returns (uint256);
}
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.0;
import {ERC20} from "solmate/tokens/ERC20.sol";
interface IBondCallback {
/// @notice Send payout tokens to Teller while allowing market owners to perform custom logic on received or paid out tokens
/// @notice Market ID on Teller must be whitelisted
/// @param id_ ID of the market
/// @param inputAmount_ Amount of quote tokens bonded to the market
/// @param outputAmount_ Amount of payout tokens to be paid out to the market
/// @dev Must transfer the output amount of payout tokens back to the Teller
/// @dev Should check that the quote tokens have been transferred to the contract in the _callback function
function callback(
uint256 id_,
uint256 inputAmount_,
uint256 outputAmount_
) external;
/// @notice Returns the number of quote tokens received and payout tokens paid out for a market
/// @param id_ ID of the market
/// @return in_ Amount of quote tokens bonded to the market
/// @return out_ Amount of payout tokens paid out to the market
function amountsForMarket(uint256 id_) external view returns (uint256 in_, uint256 out_);
/// @notice Whitelist a teller and market ID combination
/// @notice Must be callback owner
/// @param teller_ Address of the Teller contract which serves the market
/// @param id_ ID of the market
function whitelist(address teller_, uint256 id_) external;
/// @notice Remove a market ID on a teller from the whitelist
/// @dev Shutdown function in case there's an issue with the teller
/// @param teller_ Address of the Teller contract which serves the market
/// @param id_ ID of the market to remove from whitelist
function blacklist(address teller_, uint256 id_) external;
/// @notice Withdraw tokens from the callback and update balances
/// @notice Only callback owner
/// @param to_ Address of the recipient
/// @param token_ Address of the token to withdraw
/// @param amount_ Amount of tokens to withdraw
function withdraw(
address to_,
ERC20 token_,
uint256 amount_
) external;
/// @notice Deposit tokens to the callback and update balances
/// @notice Only callback owner
/// @param token_ Address of the token to deposit
/// @param amount_ Amount of tokens to deposit
function deposit(ERC20 token_, uint256 amount_) external;
}
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.0;
import {ERC20} from "solmate/tokens/ERC20.sol";
interface IBondFixedTermTeller {
// Info for bond token
struct TokenMetadata {
bool active;
ERC20 underlying;
uint8 decimals;
uint48 expiry;
uint256 supply;
}
/// @notice Deposit an ERC20 token and mint a future-dated ERC1155 bond token
/// @param underlying_ ERC20 token redeemable when the bond token vests
/// @param expiry_ Timestamp at which the bond token can be redeemed for the underlying token
/// @param amount_ Amount of underlying tokens to deposit
/// @return ID of the ERC1155 bond token received
/// @return Amount of the ERC1155 bond token received
function create(
ERC20 underlying_,
uint48 expiry_,
uint256 amount_
) external returns (uint256, uint256);
/// @notice "Deploy" a new ERC1155 bond token for an (underlying, expiry) pair and return its token ID
/// @dev ERC1155 used for fixed-term
/// @dev If a bond token exists for the (underlying, expiry) pair, it returns that token ID
/// @param underlying_ ERC20 token redeemable when the bond token vests
/// @param expiry_ Timestamp at which the bond token can be redeemed for the underlying token
/// @return ID of the ERC1155 bond token being created
function deploy(ERC20 underlying_, uint48 expiry_) external returns (uint256);
/// @notice Redeem a fixed-term bond token for the underlying token (bond token must have matured)
/// @param tokenId_ ID of the bond token to redeem
/// @param amount_ Amount of bond token to redeem
function redeem(uint256 tokenId_, uint256 amount_) external;
/// @notice Redeem multiple fixed-term bond tokens for the underlying tokens (bond tokens must have matured)
/// @param tokenIds_ Array of bond token ids
/// @param amounts_ Array of amounts of bond tokens to redeem
function batchRedeem(uint256[] memory tokenIds_, uint256[] memory amounts_) external;
/// @notice Get token ID from token and expiry
/// @param payoutToken_ Payout token of bond
/// @param expiry_ Expiry of the bond
/// @return ID of the bond token
function getTokenId(ERC20 payoutToken_, uint48 expiry_) external pure returns (uint256);
/// @notice Get the token name and symbol for a bond token
/// @param tokenId_ ID of the bond token
/// @return name Bond token name
/// @return symbol Bond token symbol
function getTokenNameAndSymbol(uint256 tokenId_)
external
view
returns (string memory, string memory);
}
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.0;
import {ERC20} from "solmate/tokens/ERC20.sol";
interface IBondTeller {
/// @notice Exchange quote tokens for a bond in a specified market
/// @param recipient_ Address of recipient of bond. Allows deposits for other addresses
/// @param referrer_ Address of referrer who will receive referral fee. For frontends to fill.
/// Direct calls can use the zero address for no referrer fee.
/// @param id_ ID of the Market the bond is being purchased from
/// @param amount_ Amount to deposit in exchange for bond
/// @param minAmountOut_ Minimum acceptable amount of bond to receive. Prevents frontrunning
/// @return Amount of payout token to be received from the bond
/// @return Timestamp at which the bond token can be redeemed for the underlying token
function purchase(
address recipient_,
address referrer_,
uint256 id_,
uint256 amount_,
uint256 minAmountOut_
) external returns (uint256, uint48);
/// @notice Get current fee charged by the teller based on the combined protocol and referrer fee
/// @param referrer_ Address of the referrer
/// @return Fee in basis points (3 decimal places)
function getFee(address referrer_) external view returns (uint48);
/// @notice Set protocol fee
/// @notice Must be guardian
/// @param fee_ Protocol fee in basis points (3 decimal places)
function setProtocolFee(uint48 fee_) external;
/// @notice Set the discount for creating bond tokens from the base protocol fee
/// @dev The discount is subtracted from the protocol fee to determine the fee
/// when using create() to mint bond tokens without using an Auctioneer
/// @param discount_ Create Fee Discount in basis points (3 decimal places)
function setCreateFeeDiscount(uint48 discount_) external;
/// @notice Set your fee as a referrer to the protocol
/// @notice Fee is set for sending address
/// @param fee_ Referrer fee in basis points (3 decimal places)
function setReferrerFee(uint48 fee_) external;
/// @notice Claim fees accrued by sender in the input tokens and sends them to the provided address
/// @param tokens_ Array of tokens to claim fees for
/// @param to_ Address to send fees to
function claimFees(ERC20[] memory tokens_, address to_) external;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "solmate/tokens/ERC20.sol";
/// @notice Safe ERC20 and ETH transfer library that safely handles missing return values.
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/libraries/TransferHelper.sol)
/// @author Taken from Solmate.
library TransferHelper {
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(ERC20.transferFrom.selector, from, to, amount)
);
require(
success &&
(data.length == 0 || abi.decode(data, (bool))) &&
address(token).code.length > 0,
"TRANSFER_FROM_FAILED"
);
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(ERC20.transfer.selector, to, amount)
);
require(
success &&
(data.length == 0 || abi.decode(data, (bool))) &&
address(token).code.length > 0,
"TRANSFER_FAILED"
);
}
// function safeApprove(
// ERC20 token,
// address to,
// uint256 amount
// ) internal {
// (bool success, bytes memory data) = address(token).call(
// abi.encodeWithSelector(ERC20.approve.selector, to, amount)
// );
// require(success && (data.length == 0 || abi.decode(data, (bool))), "APPROVE_FAILED");
// }
// function safeTransferETH(address to, uint256 amount) internal {
// (bool success, ) = to.call{value: amount}(new bytes(0));
// require(success, "ETH_TRANSFER_FAILED");
// }
}
{
"compilationTarget": {
"src/BondFixedTermTeller.sol": "BondFixedTermTeller"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 100000
},
"remappings": [
":clones-with-immutable-args/=lib/clones-with-immutable-args/src/",
":clones/=lib/clones-with-immutable-args/src/",
":ds-test/=lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":hardhat/=node_modules/hardhat/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":openzeppelin/=lib/openzeppelin-contracts/contracts/",
":solidity-code-metrics/=node_modules/solidity-code-metrics/",
":solmate/=lib/solmate/src/",
":weird-erc20/=lib/solmate/lib/weird-erc20/src/"
]
}
[{"inputs":[{"internalType":"address","name":"protocol_","type":"address"},{"internalType":"contract IBondAggregator","name":"aggregator_","type":"address"},{"internalType":"address","name":"guardian_","type":"address"},{"internalType":"contract Authority","name":"authority_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"Teller_InvalidCallback","type":"error"},{"inputs":[],"name":"Teller_InvalidParams","type":"error"},{"inputs":[],"name":"Teller_NotAuthorized","type":"error"},{"inputs":[{"internalType":"contract ERC20","name":"underlying","type":"address"},{"internalType":"uint48","name":"expiry","type":"uint48"}],"name":"Teller_TokenDoesNotExist","type":"error"},{"inputs":[{"internalType":"uint48","name":"maturesOn","type":"uint48"}],"name":"Teller_TokenNotMatured","type":"error"},{"inputs":[],"name":"Teller_UnsupportedToken","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"contract Authority","name":"newAuthority","type":"address"}],"name":"AuthorityUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"referrer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"payout","type":"uint256"}],"name":"Bonded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"contract ERC20","name":"underlying","type":"address"},{"indexed":true,"internalType":"uint48","name":"expiry","type":"uint48"}],"name":"ERC1155BondTokenCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"TransferBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TransferSingle","type":"event"},{"inputs":[],"name":"FEE_DECIMALS","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"authority","outputs":[{"internalType":"contract Authority","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"owners","type":"address[]"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"balanceOfBatch","outputs":[{"internalType":"uint256[]","name":"balances","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds_","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts_","type":"uint256[]"}],"name":"batchRedeem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20[]","name":"tokens_","type":"address[]"},{"internalType":"address","name":"to_","type":"address"}],"name":"claimFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"underlying_","type":"address"},{"internalType":"uint48","name":"expiry_","type":"uint48"},{"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"create","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"createFeeDiscount","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"underlying_","type":"address"},{"internalType":"uint48","name":"expiry_","type":"uint48"}],"name":"deploy","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"referrer_","type":"address"}],"name":"getFee","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"underlying_","type":"address"},{"internalType":"uint48","name":"expiry_","type":"uint48"}],"name":"getTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId_","type":"uint256"}],"name":"getTokenNameAndSymbol","outputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFee","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient_","type":"address"},{"internalType":"address","name":"referrer_","type":"address"},{"internalType":"uint256","name":"id_","type":"uint256"},{"internalType":"uint256","name":"amount_","type":"uint256"},{"internalType":"uint256","name":"minAmountOut_","type":"uint256"}],"name":"purchase","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId_","type":"uint256"},{"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"redeem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"referrerFees","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"contract ERC20","name":"","type":"address"}],"name":"rewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeBatchTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract Authority","name":"newAuthority","type":"address"}],"name":"setAuthority","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint48","name":"discount_","type":"uint48"}],"name":"setCreateFeeDiscount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint48","name":"fee_","type":"uint48"}],"name":"setProtocolFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint48","name":"fee_","type":"uint48"}],"name":"setReferrerFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenMetadata","outputs":[{"internalType":"bool","name":"active","type":"bool"},{"internalType":"contract ERC20","name":"underlying","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint48","name":"expiry","type":"uint48"},{"internalType":"uint256","name":"supply","type":"uint256"}],"stateMutability":"view","type":"function"}]