Skip to content

Commit

Permalink
chore-git): merge PR #10 from deploy-v1.1
Browse files Browse the repository at this point in the history
v1.1: Sale V2 and TypeChain integration
  • Loading branch information
Mathieu Bour authored Apr 11, 2022
2 parents 5b14043 + 4c4fc3f commit 4db3151
Show file tree
Hide file tree
Showing 79 changed files with 16,874 additions and 1,251 deletions.
2 changes: 1 addition & 1 deletion .solcover.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
skipFiles: ['vendor/BridgeToken.sol', 'utils/Initializer.sol'],
skipFiles: ['vendor/BridgeToken.sol', 'legacy/v1/Initializer.sol', 'legacy/v1/Sale.sol', 'ReferralProgram.sol'],
silent: false,
};
40 changes: 40 additions & 0 deletions contracts/ReferralProgram.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

contract ReferralProgram is Ownable {
IERC20Metadata public DPS;

constructor(IERC20Metadata _DPS) {
DPS = _DPS;
}

/**
* @dev Deliver DPS to recipients.
* @param recipients The recipient addresses.
* @param amounts The associated amounts.
*/
function deliver(address[] memory recipients, uint256[] memory amounts) external onlyOwner returns (bool) {
require(recipients.length != 0, "ReferralProgram: recipient length is zero");
require(recipients.length == amounts.length, "ReferralProgram: arguments size mismatch");

for (uint256 i = 0; i < recipients.length; i++) {
require(DPS.transfer(recipients[i], amounts[i]));
}

return true;
}

/**
* @dev Destroy this contract after the referral program.
*/
function destruct() external onlyOwner {
address _owner = owner();
DPS.transfer(_owner, DPS.balanceOf(address(this)));
renounceOwnership();
selfdestruct(payable(_owner));
}
}
12 changes: 4 additions & 8 deletions contracts/Sale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,9 @@ contract Sale is Ownable {
require(amountSTC >= minimumPurchaseSTC, "Sale: amount lower than minimum");
uint256 amountDPS = _validate(msg.sender, amountSTC);

// We not have to use complex low-level code as it is a simple transfer to a user wallet
payable(owner()).transfer(msg.value);
// Using .transfer() might cause an out-of-gas revert if using gnosis safe as owner
(bool sent, ) = payable(owner()).call{ value: msg.value }("");
require(sent, "Sale: failed to forward AVAX");
_transferDPS(msg.sender, amountDPS);
}

Expand Down Expand Up @@ -216,12 +217,7 @@ contract Sale is Ownable {
* @notice Close the sale by sending the remaining tokens back to the owner and then renouncing ownership.
*/
function close() external onlyOwner {
// Call the DPS owner() function
(bool success, bytes memory data) = address(DPS).staticcall(abi.encodeWithSignature("owner()"));
require(success, "Sale: unable to determine owner");
address owner = abi.decode(data, (address));

_transferDPS(owner, DPS.balanceOf(address(this)));
_transferDPS(owner(), DPS.balanceOf(address(this))); // Transfer all the DPS back to the owner
renounceOwnership();
}
}
56 changes: 56 additions & 0 deletions contracts/legacy/v1/Initializer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../../DeepSquare.sol";
import "../../Eligibility.sol";

contract Initializer is Ownable {
DeepSquare public DPS;
Eligibility public eligibility;

constructor(DeepSquare _DPS, Eligibility _eligibility) {
DPS = _DPS;
eligibility = _eligibility;
}

/**
* @dev Set multiple results in the Eligibility contract at once.
* @param accounts The accounts to validate.
* @param batch The results batch.
*/
function setResults(address[] memory accounts, Result[] memory batch) external onlyOwner {
require(accounts.length > 0, "Initializer: accounts has size 0");
require(accounts.length == batch.length, "Initializer: arguments size mismatch");

for (uint256 i = 0; i < accounts.length; i++) {
eligibility.setResult(accounts[i], batch[i]);
}
}

/**
* @dev Airdrop DPS to recipients.
* @param recipients The recipient addresses.
* @param amounts The associated amounts.
*/
function airdrop(address[] memory recipients, uint256[] memory amounts) external onlyOwner returns (bool) {
require(recipients.length != 0, "Initializer: recipient length is zero");
require(recipients.length == amounts.length, "Initializer: arguments size mismatch");

for (uint256 i = 0; i < recipients.length; i++) {
require(DPS.transfer(recipients[i], amounts[i]));
}

return true;
}

/**
* @dev Destroy this contract after the airdrop.
*/
function destruct() external onlyOwner {
address _owner = owner();
DPS.transfer(_owner, DPS.balanceOf(address(this)));
renounceOwnership();
selfdestruct(payable(_owner));
}
}
180 changes: 180 additions & 0 deletions contracts/legacy/v1/Sale.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../../Eligibility.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 eligibility contract.
IEligibility public immutable eligibility;

/// @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;

/**
* 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 _eligibility The eligibility contract.
* @param _rate The DPS/STC rate in STC cents.
* @param _initialSold How many DPS tokens were already sold.
*/
constructor(
IERC20Metadata _DPS,
IERC20Metadata _STC,
Eligibility _eligibility,
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(_eligibility) != address(0), "Sale: eligibility is zero");
require(_rate > 0, "Sale: rate is not positive");

DPS = _DPS;
STC = _STC;
eligibility = _eligibility;
rate = _rate;
minimumPurchaseSTC = _minimumPurchaseSTC;
sold = _initialSold;
}

/**
* @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.
*/
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.
*/
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 stable coin 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.
* - the account is eligible.
* @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 returns (uint256) {
require(account != owner(), "Sale: investor is the sale owner");

(uint8 tier, uint256 limit) = eligibility.lookup(account);

require(tier > 0, "Sale: account is not eligible");

uint256 investmentSTC = convertDPStoSTC(DPS.balanceOf(account)) + amountSTC;
uint256 limitSTC = limit * (10**STC.decimals());

if (limitSTC != 0) {
// zero limit means that the tier has no restrictions
require(investmentSTC <= limitSTC, "Sale: exceeds tier limit");
}

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 Buy DPS with stablecoins.
* @param amountSTC The amount of stablecoin to invest.
*/
function purchaseDPS(uint256 amountSTC) external {
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 Close the sale by sending the remaining tokens back to the owner and then renouncing ownership.
*/
function close() external onlyOwner {
// Call the DPS owner() function
(bool success, bytes memory data) = address(DPS).staticcall(abi.encodeWithSignature("owner()"));
require(success, "Sale: unable to determine owner");
address owner = abi.decode(data, (address));

_transferDPS(owner, DPS.balanceOf(address(this)));
renounceOwnership();
}
}
Loading

0 comments on commit 4db3151

Please sign in to comment.