-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathSale.sol
214 lines (183 loc) · 7.86 KB
/
Sale.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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
/**
* @title Token sale.
* @author Mathieu Bour, Julien Schneider, Charly Mancel, Valentin Pollart and Clarisse Tarrou for the DeepSquare Association.
* @notice Conduct a token sale in exchange for a stablecoin (STC), e.g. USDC.
*/
contract Sale is Ownable {
/// @notice The DPS token contract being sold. It must have an owner() function in order to let the sale be closed.
IERC20Metadata public immutable DPS;
/// @notice The stablecoin ERC20 contract.
IERC20Metadata public immutable STC;
/// @notice The Chainlink AVAX/USD pair aggregator.
AggregatorV3Interface public aggregator;
/// @notice How many cents costs a DPS (e.g., 40 means a single DPS token costs 0.40 STC).
uint8 public immutable rate;
/// @notice The minimum DPS purchase amount in stablecoin.
uint256 public immutable minimumPurchaseSTC;
/// @notice How many DPS tokens were sold during the sale.
uint256 public sold;
bool public isPaused;
/**
* Token purchase event.
* @param investor The investor address.
* @param amountDPS Amount of DPS tokens purchased.
*/
event Purchase(address indexed investor, uint256 amountDPS);
/**
* @param _DPS The DPS contract address.
* @param _STC The ERC20 stablecoin contract address (e.g, USDT, USDC, etc.).
* @param _aggregator The Chainlink AVAX/USD pair aggregator contract address.
* @param _rate The DPS/STC rate in STC cents.
* @param _initialSold How many DPS tokens were already sold.
*/
constructor(
IERC20Metadata _DPS,
IERC20Metadata _STC,
AggregatorV3Interface _aggregator,
uint8 _rate,
uint256 _minimumPurchaseSTC,
uint256 _initialSold
) {
require(address(_DPS) != address(0), "Sale: token is zero");
require(address(_STC) != address(0), "Sale: stablecoin is zero");
require(address(_aggregator) != address(0), "Sale: aggregator is zero");
require(_rate > 0, "Sale: rate is not positive");
DPS = _DPS;
STC = _STC;
aggregator = _aggregator;
rate = _rate;
minimumPurchaseSTC = _minimumPurchaseSTC;
sold = _initialSold;
isPaused = false;
}
/**
* @notice Change the Chainlink AVAX/USD pair aggregator.
* @param newAggregator The new aggregator contract address.
*/
function setAggregator(AggregatorV3Interface newAggregator) external onlyOwner {
aggregator = newAggregator;
}
/**
* @notice Convert an AVAX amount to its equivalent of the stablecoin.
* This allow to handle the AVAX purchase the same way as the stablecoin purchases.
* @param amountAVAX The amount in AVAX wei.
* @return The amount in STC.
*/
function convertAVAXtoSTC(uint256 amountAVAX) public view returns (uint256) {
(, int256 answer, , , ) = aggregator.latestRoundData();
require(answer > 0, "Sale: answer cannot be negative");
return (amountAVAX * uint256(answer) * 10**STC.decimals()) / 10**(18 + aggregator.decimals());
}
/**
* @notice Convert a stablecoin amount in DPS.
* @dev Maximum possible working value is 210M DPS * 1e18 * 1e6 = 210e30.
* Since log2(210e30) ~= 107, this cannot overflow an uint256.
* @param amountSTC The amount in stablecoin.
* @return The amount in DPS.
*/
function convertSTCtoDPS(uint256 amountSTC) public view returns (uint256) {
return (amountSTC * (10**DPS.decimals()) * 100) / rate / (10**STC.decimals());
}
/**
* @notice Convert a DPS amount in stablecoin.
* @dev Maximum possible working value is 210M DPS * 1e18 * 1e6 = 210e30.
* Since log2(210e30) ~= 107,this cannot overflow an uint256.
* @param amountDPS The amount in DPS.
* @return The amount in stablecoin.
*/
function convertDPStoSTC(uint256 amountDPS) public view returns (uint256) {
return (amountDPS * (10**STC.decimals()) * rate) / 100 / (10**DPS.decimals());
}
/**
* @notice Get the remaining DPS tokens to sell.
* @return The amount of DPS remaining in the sale.
*/
function remaining() external view returns (uint256) {
return DPS.balanceOf(address(this));
}
/**
* @notice Get the raised stablecoin amount.
* @return The amount of stablecoin raised in the sale.
*/
function raised() external view returns (uint256) {
return convertDPStoSTC(sold);
}
/**
* @notice Validate that the account is allowed to buy DPS.
* @dev Requirements:
* - the account is not the sale owner.
* @param account The account to check that should receive the DPS.
* @param amountSTC The amount of stablecoin that will be used to purchase DPS.
* @return The amount of DPS that should be transferred.
*/
function _validate(address account, uint256 amountSTC) internal view returns (uint256) {
require(account != owner(), "Sale: investor is the sale owner");
uint256 amountDPS = convertSTCtoDPS(amountSTC);
require(DPS.balanceOf(address(this)) >= amountDPS, "Sale: no enough tokens remaining");
return amountDPS;
}
/**
* @notice Deliver the DPS to the account.
* @dev Requirements:
* - there are enough DPS remaining in the sale.
* @param account The account that will receive the DPS.
* @param amountDPS The amount of DPS to transfer.
*/
function _transferDPS(address account, uint256 amountDPS) internal {
sold += amountDPS;
DPS.transfer(account, amountDPS);
emit Purchase(account, amountDPS);
}
/**
* @notice Purchase DPS with AVAX native currency.
* The invested amount will be msg.value.
*/
function purchaseDPSWithAVAX() external payable {
require(!isPaused, "Sale is paused");
uint256 amountSTC = convertAVAXtoSTC(msg.value);
require(amountSTC >= minimumPurchaseSTC, "Sale: amount lower than minimum");
uint256 amountDPS = _validate(msg.sender, amountSTC);
// Using .transfer() might cause an out-of-gas revert if using gnosis safe as owner
(bool sent, ) = payable(owner()).call{ value: msg.value }(""); // solhint-disable-line avoid-low-level-calls
require(sent, "Sale: failed to forward AVAX");
_transferDPS(msg.sender, amountDPS);
}
/**
* @notice Purchase DPS with stablecoin.
* @param amountSTC The amount of stablecoin to invest.
*/
function purchaseDPSWithSTC(uint256 amountSTC) external {
require(!isPaused, "Sale is paused");
require(amountSTC >= minimumPurchaseSTC, "Sale: amount lower than minimum");
uint256 amountDPS = _validate(msg.sender, amountSTC);
STC.transferFrom(msg.sender, owner(), amountSTC);
_transferDPS(msg.sender, amountDPS);
}
/**
* @notice Deliver DPS tokens to an investor. Restricted to the sale OWNER.
* @param amountSTC The amount of stablecoins invested, no minimum amount.
* @param account The investor address.
*/
function deliverDPS(uint256 amountSTC, address account) external onlyOwner {
uint256 amountDPS = _validate(account, amountSTC);
_transferDPS(account, amountDPS);
}
/**
* @notice Pause the sale so that only the owner can deliverDps.
*/
function setPause(bool _isPaused) external onlyOwner {
isPaused = _isPaused;
}
/**
* @notice Close the sale by sending the remaining tokens back to the owner and then renouncing ownership.
*/
function close() external onlyOwner {
DPS.transfer(owner(), DPS.balanceOf(address(this))); // Transfer all the DPS back to the owner
renounceOwnership();
}
}