-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathpTOKEN.sol
More file actions
258 lines (214 loc) · 9.91 KB
/
pTOKEN.sol
File metadata and controls
258 lines (214 loc) · 9.91 KB
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts@v4.9.3/token/ERC20/extensions/ERC20Burnable.sol";
import {Ownable2Step} from "@openzeppelin/contracts@v4.9.3/access/Ownable2Step.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts@v4.9.3/security/ReentrancyGuard.sol";
import {SafeERC20} from "@openzeppelin/contracts@v4.9.3/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts@v4.9.3/token/ERC20/IERC20.sol";
/**
* @title pTOKEN, Yield bearing token backed by ERC20
*
* @notice The backing / pTOKEN ratio is designed to appreciate for every mints and redeems that occur.
* @custom:purpose The main purpose of pTOKENs is to generate revenue and contribute to bribing economy of ve(3;3) Dexes while being backed
*
* @author Michael Damiani
*/
contract pTOKEN is ERC20Burnable, Ownable2Step, ReentrancyGuard {
using SafeERC20 for IERC20;
/**
* @custom:section ** ERRORS **
*/
error ZeroAddressNotAllowed();
error MustTradeOverMin();
error MintAndRedeemFeeNotInRange();
error TeamFeeNotInRange();
error NotStartedYet();
error ContractAlreadyFilled();
/**
* @custom:section ** IMMUTABLES **
*/
IERC20 public immutable _BACKING;
/**
* @custom:section ** CONSTANTS **
*/
uint256 private constant _MIN = 1000;
uint256 private constant _FEE_BASE_1000 = 1000;
/**
* @custom:section ** STATE VARIABLES **
*/
uint256 public MINT_AND_REDEEM_FEE = 967; // MINT_AND_REDEEM_FEE is the total amount of Fees, Fees are calculated by taking _FEE_BASE_1000 dividing MINT_AND_REDEEM_FEE and dividing by 100 to get the percentage.
uint256 public FEES = 35; //FEES include both team profit and liquidity incentives, FEES amount is sent to feeAddress that is a FeeSplitter contract which is going to split the FEES between team wallet/ treasury multisig and Liquidity incentives wallet/ multisig
uint256 public totalBacking;
address payable public feeAddress;
bool public start;
/**
* @custom:section ** EVENTS **
*/
event PriceAfterMint(
uint256 indexed time,
uint256 indexed recieved,
uint256 indexed sent
);
event PriceAfterRedeem(
uint256 indexed time,
uint256 indexed recieved,
uint256 indexed sent
);
event FeeAddressUpdated(address indexed newFeeAddress);
event TotalBackingFixed(uint256 indexed totalBackingEmergencyFixed);
event MintAndRedeemFeeUpdated(uint256 indexed MINT_AND_REDEEM_FEE);
event TeamFeeUpdated(uint256 indexed FEES);
/**
* @custom:section ** CONSTRUCTOR **
*/
constructor(address _feeAddress, address _backing) ERC20("pantheon TOKEN", "pTOKEN") Ownable2Step() {
if (_feeAddress == address(0)) revert ZeroAddressNotAllowed();
if (_backing == address(0)) revert ZeroAddressNotAllowed();
feeAddress = payable(_feeAddress);
_BACKING = IERC20(_backing);
}
/**
* @custom:section ** EXTERNAL FUNCTIONS **
*/
/**
* @param _value: Fill the contract with the first amount of _BACKING
* @notice need start = false
* @notice this function doesn't have fees, it will mint pTOKEN in a 1:1 ratio with _value
*/
function fillContract(uint256 _value) external onlyOwner {
if (start == true) revert ContractAlreadyFilled();
SafeERC20.safeTransferFrom(_BACKING, msg.sender, address(this), _value);
_mint(msg.sender, _value);
transfer(0x000000000000000000000000000000000000dEaD, 1000);
totalBacking = _BACKING.balanceOf(address(this));
}
function setStart() external onlyOwner {
start = true;
}
/**
* @param receiver: is the address that will receive the pTOKEN minted
* @notice This function is used by users that want to mint pTOKEN by depositing the corrisponding amount of Backing + a dynamic fee
* @notice The Backing / pTOKEN ratio will increase after every mint
*/
function mint(address receiver, uint256 _amount) external nonReentrant {
if (_amount < _MIN) revert MustTradeOverMin();
if (!start) revert NotStartedYet();
uint256 pToken = _BACKINGtoPTOKEN(_amount);
uint256 backingToFeeAddress = _amount / FEES;
totalBacking += (_amount - backingToFeeAddress);
_BACKING.safeTransferFrom(msg.sender, address(this), _amount);
_BACKING.safeTransfer(feeAddress, backingToFeeAddress);
_mint(receiver, (pToken * MINT_AND_REDEEM_FEE) / _FEE_BASE_1000);
emit PriceAfterMint(block.timestamp, pToken, _amount);
}
/**
* @param _amount: is the amount of pTOKEN that the user want to burn in order to redeem the corrisponding amount of Backing
* @notice This function is used by users that want to burn their balance of pTOKEN and redeem the corrisponding amount of Backing - dynamic fees from 2% up to 5%
* @notice The Backing / pTOKEN ratio will increase after every redeem
*/
function redeem(uint256 _amount) external nonReentrant {
if (_amount < _MIN) revert MustTradeOverMin();
uint256 backing = _PTOKENtoBACKING(_amount);
uint256 backingToFeeAddress = backing / FEES;
uint256 backingToSender = (backing * MINT_AND_REDEEM_FEE) / _FEE_BASE_1000;
totalBacking -= (backingToSender + backingToFeeAddress);
_burn(msg.sender, _amount);
SafeERC20.safeTransfer(_BACKING, feeAddress, backingToFeeAddress);
SafeERC20.safeTransfer(_BACKING, msg.sender, backingToSender);
emit PriceAfterRedeem(block.timestamp, _amount, backing);
}
/**
* @param _address: The Address that will receive the Liquidty Incentives and Team Fee
* @notice This function will be used to update the feeAddress
*/
function setFeeAddress(address _address) external onlyOwner {
_assemblyOwnerNotZero(_address);
feeAddress = payable(_address);
emit FeeAddressUpdated(_address);
}
/**
* @param _amount: total fee amount, 1000 / _amount / 100 = total fee percentage
* @notice this function is used by Owner to modify the total FEE %
*/
function setMintAndRedeemFee(uint16 _amount) external onlyOwner {
if(_amount > 990 || _amount < 960) revert MintAndRedeemFeeNotInRange();
MINT_AND_REDEEM_FEE = _amount;
emit MintAndRedeemFeeUpdated(_amount);
}
/**
* @param _amount:
* @notice Team fee can't be more than 3.33% and less than 1%
* @notice Incentives for liquidity providers are included in the Team fee (FEES)
*/
function setTeamFee(uint16 _amount) external onlyOwner {
if(_amount > 100 || _amount < 30) revert TeamFeeNotInRange();
FEES = _amount;
emit TeamFeeUpdated(_amount);
}
/**
* @notice This function is used to reflect the correct amount of totalBacking in case some unexpected bug occur
*/
function emergencyFixTotalBacking() external onlyOwner {
totalBacking = _BACKING.balanceOf(address(this));
emit TotalBackingFixed(address(this).balance);
}
/**
* @custom:section ** INTERNAL FUNCTIONS **
*/
/**
* @custom:section ** PRIVATE FUNCTIONS **
*/
/**
* @param _value: is the amount of backing
* @notice This function is used inside other function to get the current backing / pTOKEN ratio
*/
function _PTOKENtoBACKING(uint256 _value) private view returns (uint256) {
return (_value * totalBacking) / totalSupply();
}
/**
* @param _value: is the amount of pTOKENs
* @notice This function is used inside other function to get the current pTOKEN / Backing ratio
*/
function _BACKINGtoPTOKEN(uint256 _value) private view returns (uint256) {
return (_value * totalSupply()) / (totalBacking);
}
/**
* @param _addr: Is the address used in the functions that call the _assemblyOwnerNotZero function
* @notice This function is used inside other function to check that the address put as parameter is different from the zero address. It saves gas compared to a if statement + a revert with a custom error
*/
function _assemblyOwnerNotZero(address _addr) private pure {
assembly {
if iszero(_addr) {
mstore(0x00, "Zero address")
revert(0x00, 0x20)
}
}
}
/**
* @custom:section ** EXTERNAL VIEW / PURE FUNCTIONS **
*/
/**
* @param _amount: is the amount of backing
* @notice This function is used inside other function to get the current Redeem price of pTOKEN in Backing
*/
function getRedeemPrice(uint256 _amount) external view returns (uint256) {
uint256 ratio = _PTOKENtoBACKING(_amount);
uint256 redeemPrice = (ratio * MINT_AND_REDEEM_FEE) / _FEE_BASE_1000;
return redeemPrice;
}
/**
* @param _amount: is the amount of backing
* @notice This function is used inside other function to get the current Mint price of pTOKEN in Backing
*/
function getMintPrice(uint256 _amount) external view returns (uint256) {
uint256 ratio = _PTOKENtoBACKING(_amount);
uint256 mintPrice = (ratio * _FEE_BASE_1000) / MINT_AND_REDEEM_FEE;
return mintPrice;
}
/**
* @notice This function is used inside other function to get the total amount of Backing in the contract
*/
function getTotalBacking() external view returns (uint256) {
return totalBacking;
}
}