Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement reward program contracts for each whitepaper section #16

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions contracts/Advisors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./Transfer.sol";

contract Advisors is Transfer {
constructor(
IERC20Metadata _DPS,
IERC20Metadata _STC,
Eligibility _eligibility,
uint8 _rate
) Transfer (
_DPS,
_STC,
_eligibility,
_rate
) {}
}
18 changes: 18 additions & 0 deletions contracts/CommunityDev.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./Transfer.sol";

contract CommunityDev is Transfer {
constructor(
IERC20Metadata _DPS,
IERC20Metadata _STC,
Eligibility _eligibility,
uint8 _rate
) Transfer (
_DPS,
_STC,
_eligibility,
_rate
) {}
}
18 changes: 18 additions & 0 deletions contracts/Team.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./Transfer.sol";

contract Team is Transfer {
constructor(
IERC20Metadata _DPS,
IERC20Metadata _STC,
Eligibility _eligibility,
uint8 _rate
) Transfer (
_DPS,
_STC,
_eligibility,
_rate
) {}
}
123 changes: 123 additions & 0 deletions contracts/Transfer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// 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";
import "./Eligibility.sol";

/**
* @title Token transfer.
* @author Valentin Pollart for the DeepSquare Association.
* @notice Conduct a token transfer.
*/
contract Transfer 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;

/**
* Token purchase event.
* @param investor The investor address.
* @param amountDPS Amount of DPS tokens purchased.
*/
event TransferEvent(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.
*/
constructor(
IERC20Metadata _DPS,
IERC20Metadata _STC,
Eligibility _eligibility,
uint8 _rate
) {
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;
}

/**
* @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 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 amountDPS The amount of DPS that will be tranferred.
*/
function _validate(address account, uint256 amountDPS) internal {
require(account != owner(), "Transfer: beneficiary is the sale owner");
require(DPS.balanceOf(address(this)) >= amountDPS, "Transfer: no enough tokens remaining");

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

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

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

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

/**
* @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 {
DPS.transfer(account, amountDPS);

emit TransferEvent(account, amountDPS);
}

/**
* @notice Deliver DPS tokens to an investor. Restricted to the sale OWNER.
* @param amountDPS The amount of DPS transferred, no minimum amount.
* @param account The investor address.
*/
function deliverDPS(uint256 amountDPS, address account) external onlyOwner {
_validate(account, amountDPS);
_transferDPS(account, amountDPS);
}

/**
* @notice Close the sale by sending the remaining tokens back to the owner and then renouncing ownership.
*/
function close() external onlyOwner {
_transferDPS(owner(), DPS.balanceOf(address(this))); // Transfer all the DPS back to the owner
renounceOwnership();
}
}
18 changes: 18 additions & 0 deletions contracts/Treasury.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./Transfer.sol";

contract Treasury is Transfer {
constructor(
IERC20Metadata _DPS,
IERC20Metadata _STC,
Eligibility _eligibility,
uint8 _rate
) Transfer (
_DPS,
_STC,
_eligibility,
_rate
) {}
}
95 changes: 62 additions & 33 deletions scripts/deliver.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,77 @@
import { Presets, SingleBar } from 'cli-progress';
import { ethers } from 'hardhat';
import { BigNumber } from '@ethersproject/bignumber';
import { parseEther } from '@ethersproject/units';
import mappings from '../data/mappings.json';
import { ethers, network } from 'hardhat';
import { Advisors__factory } from '../typings/factories/contracts/Advisors__factory';
import { CommunityDev__factory } from '../typings/factories/contracts/CommunityDev__factory';
import { DeepSquare__factory } from '../typings/factories/contracts/DeepSquare__factory';
import { ReferralProgram__factory } from '../typings/factories/contracts/ReferralProgram__factory';
import { Team__factory } from '../typings/factories/contracts/Team__factory';
import { Treasury__factory } from '../typings/factories/contracts/Treasury__factory';

const LIMIT = parseEther('25000');
type NetworkName = 'hardhat' | 'mainnet' | 'fuji';
type ContractName = 'STC' | 'DeepSquare' | 'Eligibility';

async function main() {
const [deployer] = await ethers.getSigners();
const DPS = new DeepSquare__factory(deployer).attach('0xf192cae2e7cd4048bea307368015e3647c49338e');
const amounts: Record<string, string> = {};
const addresses: Record<ContractName, Record<NetworkName, string>> = {
STC: {
hardhat: '0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e',
mainnet: '0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e',
fuji: '0x40479524A1D4E8E160CB24B5D466e2a336327331',
},
DeepSquare: {
hardhat: '0xf192caE2e7Cd4048Bea307368015E3647c49338e',
mainnet: '0xf192cae2e7cd4048bea307368015e3647c49338e',
fuji: '0x7E8Ab41d9Fd280AAac0643d011E6241F6e540497',
},
Eligibility: {
hardhat: '0x52088e60AfB56E83cA0B6340B49F709e57973869',
mainnet: '0x52088e60AfB56E83cA0B6340B49F709e57973869',
fuji: '0x64f8dE5BE4403E30e58394D3337d547F59be5035',
},
};

const progress = new SingleBar({}, Presets.shades_classic);
progress.start(Object.keys(mappings).length, 0);
async function main() {
const networkName = network.name as NetworkName;

for (const [beneficiary, referees] of Object.entries(mappings)) {
progress.increment();
const actualBalance = await DPS.balanceOf(beneficiary);
const [deployer] = await ethers.getSigners();

if (actualBalance.lt(LIMIT)) {
continue; // User has not invested enough to 25000 DPS
}
const DPS = new DeepSquare__factory(deployer).attach(addresses.DeepSquare[networkName]);
const gnosisAddress = await DPS.owner();

const balances = await Promise.all(referees.map((referee) => DPS.balanceOf(referee)));
const sum = balances.reduce((total, value) => total.add(value), BigNumber.from(0));
const Advisors = await new Advisors__factory(deployer).deploy(
addresses.DeepSquare[networkName],
addresses.STC[networkName],
addresses.Eligibility[networkName],
40,
);
await Advisors.deployed().then((c) => console.log('Advisors contract deployed at ', c.address));

if (sum.gt(0)) {
amounts[beneficiary] = sum.mul(12).div(100).toString();
}
}
progress.stop();
const CommunityDev = await new CommunityDev__factory(deployer).deploy(
addresses.DeepSquare[networkName],
addresses.STC[networkName],
addresses.Eligibility[networkName],
40,
);
await CommunityDev.deployed().then((c) => console.log('CommunityDev contract deployed at ', c.address));

// deliver the DPS to the referrers, but we need to have at least the sum of the referral gains
// for (const [beneficiary, amount] of Object.entries(amounts)) {
// await DPS.transfer(beneficiary, amount); // but we need some DPS!! // 1 AVAX tx
// }
const Team = await new Team__factory(deployer).deploy(
addresses.DeepSquare[networkName],
addresses.STC[networkName],
addresses.Eligibility[networkName],
40,
);
await Team.deployed().then((c) => console.log('Team contract deployed at ', c.address));

const ReferralProgram = await new ReferralProgram__factory(deployer).deploy(
'0xf192cae2e7cd4048bea307368015e3647c49338e',
const Treasury = await new Treasury__factory(deployer).deploy(
addresses.DeepSquare[networkName],
addresses.STC[networkName],
addresses.Eligibility[networkName],
40,
);
await Treasury.deployed().then((c) => console.log('Treasury contract deployed at ', c.address));

await Advisors.transferOwnership(gnosisAddress);
await CommunityDev.transferOwnership(gnosisAddress);
await Team.transferOwnership(gnosisAddress);
await Treasury.transferOwnership(gnosisAddress);

await ReferralProgram.deliver(Object.keys(amounts), Object.values(amounts)); // order ?
console.log('Ownership of contracts tranferred to ', gnosisAddress);
}

main().catch((e) => {
Expand Down
Empty file.
Loading