Skip to content

Commit 88a0008

Browse files
authored
EMFXD-1689: Merge private1 and private2 (#3) (#4)
* refactor: ♻️ upgrade private 2 to merge with the private 1 * chore: 🐛 update vault to point to vault private 1
1 parent e15c5f1 commit 88a0008

File tree

6 files changed

+189
-49
lines changed

6 files changed

+189
-49
lines changed

contracts/FoxtrotPrivateSale.sol

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ pragma solidity 0.8.4;
33

44
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
55
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6-
import "@openzeppelin/contracts/access/Ownable.sol";
6+
import "contracts/utils/Whitelist.sol";
77

88
/**
99
* @title Automatic Private sale
1010
* @author Michael Araque
1111
* @notice A contract that manages a Public Private Sale, purchase, claiming and vesting time
1212
*/
1313

14-
contract FoxtrotPrivateSale is Ownable {
14+
contract FoxtrotPrivateSale is Whitelist {
1515
enum InvestorTrace {
1616
CLAIMED,
1717
LOCKED,
@@ -30,40 +30,46 @@ contract FoxtrotPrivateSale is Ownable {
3030
mapping(address => mapping(InvestorTrace => uint256)) private accounting;
3131
mapping(ContractDates => uint256) private dates;
3232

33+
event UpdatePrivateSaleStatus(bool isOpen);
3334
event ClaimToken(address tokenAddress, uint256 tokenAmount);
3435
event Invest(address investor, uint256 busdAmount, uint256 tokenAmount);
3536

3637
address public busdContract;
3738
address public tokenContract;
3839
address public companyVault;
3940

41+
bool public isPrivateSaleOpen;
4042
bool public isClaimEnabled;
4143
uint256 private tokensSoldCounter;
4244
uint256 public totalBusdInvested;
4345

4446
uint256 private immutable TGE_PERCENT = 8;
4547
uint256 private immutable AFTER_TGE_BLOCK_TIME = 90 days;
46-
uint256 private immutable FXD_PRICE = 30000000000000000 wei;
47-
uint256 private immutable MIN_BUSD_ACCEPTED = 50 ether;
48-
uint256 private constant MAX_AMOUNT_TOKEN = 15050000 ether;
48+
uint256 private immutable FXD_PRICE = 25000000000000000 wei;
49+
uint256 private immutable MIN_BUSD_ACCEPTED = 1 ether;
50+
uint256 private constant MAX_AMOUNT_TOKEN = 32_250_000 ether;
4951

5052
constructor(address _companyVault, address _busdContract) {
5153
companyVault = _companyVault;
5254
busdContract = _busdContract;
5355
tokenContract = address(0);
56+
Whitelist.isWhitelistEnabled = true;
5457

5558
tokensSoldCounter = MAX_AMOUNT_TOKEN;
5659

5760
dates[ContractDates.SALE_START] = 1665504776;
58-
dates[ContractDates.SALE_END] = 1728663176;
5961
dates[ContractDates.VESTING_PERIOD] = 360 days;
62+
63+
isPrivateSaleOpen = true;
6064
}
6165

6266
/**
6367
* @dev This function allows to invest in the private sale
6468
* @param amount Amount in BUSD to be invested in wei format
6569
*/
66-
function invest(uint256 amount) public {
70+
function invest(uint256 amount) public onlyWhitelisted {
71+
require(isPrivateSaleOpen, "FXD: Private Sale is closed");
72+
6773
require(
6874
IERC20(busdContract).balanceOf(msg.sender) >= amount,
6975
"FXD: Insufficient BUSD"
@@ -74,15 +80,23 @@ contract FoxtrotPrivateSale is Ownable {
7480
);
7581
require(
7682
block.timestamp >= dates[ContractDates.SALE_START],
77-
"FXD: Private Sale not started"
78-
);
79-
require(
80-
block.timestamp <= dates[ContractDates.SALE_END],
81-
"FXD: Private Sale ended"
83+
"FXD: Private Sale not started yet"
8284
);
8385

86+
if (Whitelist.isWhitelistEnabled) {
87+
require(
88+
accounting[msg.sender][InvestorTrace.BUSD_INVESTED] <=
89+
Whitelist.amount[msg.sender] &&
90+
amount <= Whitelist.amount[msg.sender] &&
91+
accounting[msg.sender][InvestorTrace.BUSD_INVESTED] +
92+
amount <=
93+
Whitelist.amount[msg.sender],
94+
"FXD: Private Sale purchase limit"
95+
);
96+
}
97+
8498
if (tokensSoldCounter >= getTokenAmount(MIN_BUSD_ACCEPTED, FXD_PRICE))
85-
require(amount >= MIN_BUSD_ACCEPTED, "FXD: Minimum amount 50 BUSD");
99+
require(amount >= MIN_BUSD_ACCEPTED, "FXD: Minimum amount 1 BUSD");
86100

87101
uint256 tokensAmount = getTokenAmount(amount, FXD_PRICE);
88102
require(
@@ -134,7 +148,7 @@ contract FoxtrotPrivateSale is Ownable {
134148
* @dev ClaimToken Emit event
135149
* @notice This method is the main method to claim tokens
136150
*/
137-
function claim() external {
151+
function claim() external onlyWhitelisted {
138152
require(isClaimEnabled, "FXD: Claim status inactive");
139153
require(
140154
accounting[msg.sender][InvestorTrace.LOCKED] > 0,
@@ -328,7 +342,7 @@ contract FoxtrotPrivateSale is Ownable {
328342
* @param from Address of the investor
329343
* @return uint256 Returns the total amount of tokens that the investor has invested
330344
*/
331-
function historicalBalance(address from) internal view returns (uint256) {
345+
function historicalBalance(address from) external view returns (uint256) {
332346
return (accounting[from][InvestorTrace.LOCKED] +
333347
accounting[from][InvestorTrace.CLAIMED]);
334348
}
@@ -347,18 +361,26 @@ contract FoxtrotPrivateSale is Ownable {
347361
}
348362

349363
/**
350-
* @notice This method is a helper function that allows to set the end of the sale manually
364+
* @notice This method is a helper function that allows to close the private sale manually
351365
*/
352-
function setSaleEnd() external onlyOwner returns (bool) {
353-
dates[ContractDates.SALE_END] = block.timestamp;
354-
return true;
366+
function setSaleEnd() external onlyOwner {
367+
isPrivateSaleOpen = false;
368+
emit UpdatePrivateSaleStatus(false);
355369
}
356370

357371
/**
358-
* @return uint256 Date of the Private sale end
372+
* @notice This method is a helper function that allows to open the private sale manually
359373
*/
360-
function getSaleEnd() external view returns (uint256) {
361-
return dates[ContractDates.SALE_END];
374+
function openPrivateSale() external onlyOwner {
375+
isPrivateSaleOpen = true;
376+
emit UpdatePrivateSaleStatus(true);
377+
}
378+
379+
/**
380+
* @return bool Show is the privatesale is open or closed
381+
*/
382+
function showPrivateSaleStatus() external view returns (bool) {
383+
return isPrivateSaleOpen;
362384
}
363385

364386
/**
@@ -398,7 +420,10 @@ contract FoxtrotPrivateSale is Ownable {
398420
"FXD: You can't withdraw Foxtrot Tokens"
399421
);
400422
IERC20 Token = IERC20(token);
401-
require(Token.balanceOf(address(this)) >= amount, "FXD: Insufficient amount");
423+
require(
424+
Token.balanceOf(address(this)) >= amount,
425+
"FXD: Insufficient amount"
426+
);
402427
Token.transfer(receiver, amount);
403428
return true;
404429
}
@@ -408,10 +433,6 @@ contract FoxtrotPrivateSale is Ownable {
408433
* to the Foxtrot Command (FXD) Contract
409434
*/
410435
function purgeNonSelledTokens() external onlyOwner {
411-
require(
412-
block.timestamp >= dates[ContractDates.SALE_END],
413-
"FXD: Private sale is still alive"
414-
);
415436
SafeERC20.safeTransfer(
416437
IERC20(tokenContract),
417438
tokenContract,

contracts/utils/Whitelist.sol

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.4;
3+
4+
import "@openzeppelin/contracts/access/Ownable.sol";
5+
6+
/**
7+
* @title Whitelist
8+
* @dev The Whitelist contract has a whitelist of addresses, and provides basic authorization control functions.
9+
* @dev This simplifies the implementation of "user permissions".
10+
*/
11+
contract Whitelist is Ownable {
12+
bool public isWhitelistEnabled;
13+
14+
mapping(address => bool) public whitelist;
15+
mapping(address => uint256) public amount;
16+
17+
event WhitelistedAddressAdded(address addr, uint256 amount);
18+
event WhitelistedAddressRemoved(address addr);
19+
event WhitelistEnabled(address who);
20+
event WhitelistDisabled(address who);
21+
22+
modifier onlyWhitelisted() {
23+
if (isWhitelistEnabled) {
24+
require(whitelist[msg.sender], "FXD: Not on the whitelist");
25+
}
26+
_;
27+
}
28+
29+
function addAddressToWhitelist(address addr, uint256 _amount)
30+
public
31+
onlyOwner
32+
returns (bool success)
33+
{
34+
if (!whitelist[addr]) {
35+
whitelist[addr] = true;
36+
amount[addr] = _amount;
37+
emit WhitelistedAddressAdded(addr, _amount);
38+
success = true;
39+
}
40+
}
41+
42+
function addAddressesToWhitelist(
43+
address[] calldata addrs,
44+
uint256[] calldata _amount
45+
) public onlyOwner returns (bool success) {
46+
for (uint256 i = 0; i < addrs.length; i++) {
47+
if (addAddressToWhitelist(addrs[i], _amount[i])) {
48+
success = true;
49+
}
50+
}
51+
}
52+
53+
function removeAddressFromWhitelist(address addr)
54+
public
55+
onlyOwner
56+
returns (bool success)
57+
{
58+
if (whitelist[addr]) {
59+
whitelist[addr] = false;
60+
amount[addr] = 0;
61+
emit WhitelistedAddressRemoved(addr);
62+
success = true;
63+
}
64+
}
65+
66+
function removeAddressesFromWhitelist(address[] calldata addrs)
67+
public
68+
onlyOwner
69+
returns (bool success)
70+
{
71+
for (uint256 i = 0; i < addrs.length; i++) {
72+
if (removeAddressFromWhitelist(addrs[i])) {
73+
success = true;
74+
}
75+
}
76+
}
77+
78+
function disableWhitelist() external onlyOwner {
79+
isWhitelistEnabled = false;
80+
emit WhitelistDisabled(msg.sender);
81+
}
82+
83+
function enableWhitelist() external onlyOwner {
84+
isWhitelistEnabled = true;
85+
emit WhitelistEnabled(msg.sender);
86+
}
87+
88+
}

scripts/deploy.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ async function main() {
2525
await busdToken.deployed();
2626
await privateSale.deployed();
2727

28+
await privateSale.connect(masterAccount).addAddressToWhitelist(masterAccount.address, ethers.utils.parseEther('120000'));
29+
2830
await foxtrotToken
2931
.connect(masterAccount)
3032
.secureTransfer(
@@ -41,7 +43,7 @@ async function main() {
4143
console.log("Token deployed to:", foxtrotToken.address);
4244

4345
console.log(`
44-
NEXT_PUBLIC_PRIVATE_TWO_CONTRACT=${privateSale.address}
46+
NEXT_PUBLIC_PRIVATE_CONTRACT=${privateSale.address}
4547
NEXT_PUBLIC_BUSD_CONTRACT=${busdToken.address}
4648
NEXT_PUBLIC_FXD_CONTRACT=${foxtrotToken.address}
4749
`);

scripts/deployPrivateSale.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ import hre from "hardhat";
33

44
async function main() {
55
let masterAccount;
6-
let privateSaleVault2 = "0x5Cb9dDb9e859CF87fE515597306483D7198B9471";
6+
let privateSaleVault = "0x7d9e52c4d53abcf6efa95ac0883925e326f92730";
77
let fxdToken = "0x15df8d414ab0add3e989af15fea7d279e4c2c58a";
88
let busdToken = "0xe9e7cea3dedca5984780bafc599bd69add087d56";
99

1010
[masterAccount] = await ethers.getSigners();
1111

1212
const PrivateSale = await hre.ethers.getContractFactory("FoxtrotPrivateSale");
13-
const privateSale = await PrivateSale.deploy(privateSaleVault2, busdToken);
13+
const privateSale = await PrivateSale.deploy(privateSaleVault, busdToken);
1414

1515
try {
1616
await hre.run("verify:verify", {
1717
address: privateSale.address,
1818
contract: "contracts/FoxtrotPrivateSale.sol:FoxtrotPrivateSale",
19-
constructorArguments: [privateSaleVault2, busdToken],
19+
constructorArguments: [privateSaleVault, busdToken],
2020
});
2121
} catch (err: any) {
2222
if (err.message.includes("Reason: Already Verified")) {

tasks/changeClaimStatus.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ task("change-claim", "Change claim status")
1010
}
1111

1212
const [deployer] = await hre.ethers.getSigners();
13-
const privateSale = await (
13+
const privateSale = (
1414
await hre.ethers.getContractFactory("FoxtrotPrivateSale")
1515
).attach(taskArgs.contract);
1616
await privateSale.connect(deployer).changeClaimStatus();

0 commit comments

Comments
 (0)