This repository has been archived by the owner on May 26, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
BondFixedExpiryTeller.sol
194 lines (165 loc) · 7.91 KB
/
BondFixedExpiryTeller.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.8.15;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {ClonesWithImmutableArgs} from "clones/ClonesWithImmutableArgs.sol";
import {BondBaseTeller, IBondAggregator, Authority} from "./bases/BondBaseTeller.sol";
import {IBondFixedExpiryTeller} from "./interfaces/IBondFixedExpiryTeller.sol";
import {ERC20BondToken} from "./ERC20BondToken.sol";
import {TransferHelper} from "./lib/TransferHelper.sol";
import {FullMath} from "./lib/FullMath.sol";
/// @title Bond Fixed Expiry Teller
/// @notice Bond Fixed Expiry 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 Expiry Teller is an implementation of the
/// Bond Base Teller contract specific to handling user bond transactions
/// and tokenizing bond markets where all purchases vest at the same timestamp
/// as ERC20 tokens.
///
/// @author Oighty, Zeus, Potted Meat, indigo
contract BondFixedExpiryTeller is BondBaseTeller, IBondFixedExpiryTeller {
using TransferHelper for ERC20;
using FullMath for uint256;
using ClonesWithImmutableArgs for address;
/* ========== EVENTS ========== */
event ERC20BondTokenCreated(
ERC20BondToken bondToken,
ERC20 indexed underlying,
uint48 indexed expiry
);
/* ========== STATE VARIABLES ========== */
/// @notice ERC20 bond tokens (unique to a underlying and expiry)
mapping(ERC20 => mapping(uint48 => ERC20BondToken)) public bondTokens;
/// @notice ERC20BondToken reference implementation (deployed on creation to clone from)
ERC20BondToken public immutable bondTokenImplementation;
/* ========== CONSTRUCTOR ========== */
constructor(
address protocol_,
IBondAggregator aggregator_,
address guardian_,
Authority authority_
) BondBaseTeller(protocol_, aggregator_, guardian_, authority_) {
bondTokenImplementation = new ERC20BondToken();
}
/* ========== PURCHASE ========== */
/// @notice Handle payout to recipient
/// @param recipient_ Address to receive payout
/// @param payout_ Amount of payoutToken to be paid
/// @param underlying_ Token to be paid out
/// @param vesting_ Timestamp when the payout will vest
/// @return expiry Timestamp when the payout will vest
function _handlePayout(
address recipient_,
uint256 payout_,
ERC20 underlying_,
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-expiry bonds mature at a set timestamp
// i.e. expiry = day 10. when alice deposits on day 1, her term
// is 9 days. when bob deposits on day 2, his term is 8 days.
if (vesting_ > uint48(block.timestamp)) {
expiry = vesting_;
// Fixed-expiry bonds mint ERC-20 tokens
bondTokens[underlying_][expiry].mint(recipient_, payout_);
} else {
// If no expiry, then transfer payout directly to user
underlying_.safeTransfer(recipient_, payout_);
}
}
/* ========== DEPOSIT/MINT ========== */
/// @inheritdoc IBondFixedExpiryTeller
function create(
ERC20 underlying_,
uint48 expiry_,
uint256 amount_
) external override nonReentrant returns (ERC20BondToken, uint256) {
// Revert if expiry is in the past
if (expiry_ < block.timestamp) revert Teller_InvalidParams();
ERC20BondToken bondToken = bondTokens[underlying_][expiry_];
// Revert if no token exists, must call deploy first
if (bondToken == ERC20BondToken(address(0x00)))
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
bondToken.mint(msg.sender, amount_ - feeAmount);
return (bondToken, amount_ - feeAmount);
} else {
// Mint new bond tokens
bondToken.mint(msg.sender, amount_);
return (bondToken, amount_);
}
}
/* ========== REDEEM ========== */
/// @inheritdoc IBondFixedExpiryTeller
function redeem(ERC20BondToken token_, uint256 amount_) external override nonReentrant {
// Validate token is issued by this teller
ERC20 underlying = token_.underlying();
uint48 expiry = token_.expiry();
if (token_ != bondTokens[underlying][expiry]) revert Teller_UnsupportedToken();
// Validate token expiry has passed
if (uint48(block.timestamp) < expiry) revert Teller_TokenNotMatured(expiry);
// Burn bond token and transfer underlying
token_.burn(msg.sender, amount_);
underlying.safeTransfer(msg.sender, amount_);
}
/* ========== TOKENIZATION ========== */
/// @inheritdoc IBondFixedExpiryTeller
function deploy(ERC20 underlying_, uint48 expiry_)
external
override
nonReentrant
returns (ERC20BondToken)
{
// Revert if expiry is in the past
if (uint256(expiry_) < block.timestamp) revert Teller_InvalidParams();
// Create bond token if one doesn't already exist
ERC20BondToken bondToken = bondTokens[underlying_][expiry_];
if (bondToken == ERC20BondToken(address(0))) {
(string memory name, string memory symbol) = _getNameAndSymbol(underlying_, expiry_);
bytes memory tokenData = abi.encodePacked(
bytes32(bytes(name)),
bytes32(bytes(symbol)),
uint8(underlying_.decimals()),
underlying_,
uint256(expiry_),
address(this)
);
bondToken = ERC20BondToken(address(bondTokenImplementation).clone(tokenData));
bondTokens[underlying_][expiry_] = bondToken;
emit ERC20BondTokenCreated(bondToken, underlying_, expiry_);
}
return bondToken;
}
/// @inheritdoc IBondFixedExpiryTeller
function getBondTokenForMarket(uint256 id_) external view override returns (ERC20BondToken) {
(, , ERC20 underlying, , uint48 vesting, ) = _aggregator
.getAuctioneer(id_)
.getMarketInfoForPurchase(id_);
return bondTokens[underlying][vesting];
}
}