Skip to content

Commit

Permalink
ICO contract and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
antico5 committed Nov 1, 2021
1 parent 48842d3 commit 9d253d2
Show file tree
Hide file tree
Showing 11 changed files with 32,863 additions and 0 deletions.
2 changes: 2 additions & 0 deletions ico/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALCHEMY_API_KEY=
ROPSTEN_PRIVATE_KEY=
7 changes: 7 additions & 0 deletions ico/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
.env

#Hardhat files
cache
artifacts
dist
16 changes: 16 additions & 0 deletions ico/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
"explicitTypes": "always",
"semi": true
}
}
]
}
77 changes: 77 additions & 0 deletions ico/contracts/ICO.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-3.0

/*
Requirements:
- Must receive eth in exchange for an ERC20 token
- There is a recipient address that receives all incoming eth.
- Exchange rate is fixed. 1 eth = 1000 tokens
- Minimum investment is 0.01 eth and maximum is 5 eth.
- The ICO hardcap is 300 eth.
- An admin decides when the ICO starts and ends.
- The ICO ends when the hardcap is reached or end time is reached.
- The token will be tradeable only after a specific time set by the admin
*/

pragma solidity ^0.8.7;

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

uint256 constant TOKENS_PER_ETH = 1000;
uint256 constant HARDCAP = 10 ether;
uint256 constant MIN_INVESTMENT = 0.01 ether;
uint256 constant MAX_INVESTMENT = 5 ether;

contract ICO is Ownable {
address payable recipient; // address that will receive all invested eth
uint256 totalRaised; // keep track of raised eth
uint256 public startBlock; // block after which investment is allowed
uint256 public endBlock; // block until which investment is allowed
Token public token; // ERC20 token minted from this ICO

event Invested(address indexed investor, uint256 etherValue, uint256 tokensReceived);

constructor(
address payable _recipient,
uint256 _startBlock,
uint256 _endBlock,
uint256 _tradeableAfterBlock
) {
recipient = _recipient;
startBlock = _startBlock;
endBlock = _endBlock;
token = new Token(_tradeableAfterBlock);
}

modifier notFinished() {
require(block.number >= startBlock, "ICO not started yet");
require(block.number <= endBlock, "ICO already finished");
_;
}

function invest() public payable notFinished {
require(msg.value >= MIN_INVESTMENT, "Minimum investment is 0.01 eth");
require(msg.value <= MAX_INVESTMENT, "Maximum investment is 5 eth");

// Ensure hardcap is not passed even with current investment
totalRaised += msg.value;
require(totalRaised <= HARDCAP, "Hardcap exceeded");

// Send eth to the contract's recipient
recipient.transfer(msg.value);

// Mint tokens for the investor
uint256 tokens = msg.value * TOKENS_PER_ETH;
token.mint(msg.sender, tokens);

emit Invested(msg.sender, msg.value, tokens);
}

function setBlocks(uint256 _startBlock, uint256 _endBlock) public onlyOwner {
startBlock = _startBlock;
endBlock = _endBlock;
}
}
29 changes: 29 additions & 0 deletions ico/contracts/Token.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-3.0

// ICO Token

pragma solidity ^0.8.7;

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

contract Token is ERC20('Terra Vision', 'TVI'), Ownable {
uint tradeableAfterBlock;

constructor(uint _tradeableAfterBlock){
tradeableAfterBlock = _tradeableAfterBlock;
}

function mint(address account, uint amount) public onlyOwner {
_mint(account, amount);
}

function _transfer(address sender, address recipient, uint amount) internal override {
require(block.number >= tradeableAfterBlock, "Token is not tradeable yet");
ERC20._transfer(sender, recipient, amount);
}

function setTradeableAfterBlock(uint _tradeableAfterBlock) public onlyOwner {
tradeableAfterBlock = _tradeableAfterBlock;
}
}
41 changes: 41 additions & 0 deletions ico/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { task } from "hardhat/config";
import "@nomiclabs/hardhat-waffle";
import "hardhat-gas-reporter"
import "hardhat-tracer"
import "@atixlabs/hardhat-time-n-mine"
import "@atixlabs/hardhat-time-n-mine/dist/src/type-extensions";

import dotenv from 'dotenv'
dotenv.config()

// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (args, hre) => {
const accounts = await hre.ethers.getSigners();

for (const account of accounts) {
console.log(await account.address);
}
});

// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more

export default {
solidity: {
version: '0.8.7',
settings: {
outputSelection: {
"*": {
"*": ["storageLayout"],
},
},
}
},
networks: {
ropsten: {
url: `https://eth-ropsten.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
accounts: [`0x${process.env.ROPSTEN_PRIVATE_KEY}`],
},
},
};
Loading

0 comments on commit 9d253d2

Please sign in to comment.