Skip to content

Instantly share code, notes, and snippets.

@stableproject
Created September 2, 2017 16:07
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save stableproject/3f9cb84438dadf1b4160c37fe9fd7b02 to your computer and use it in GitHub Desktop.
STABLE Project ICO
pragma solidity ^0.4.11;
/// @title STABLE Project ICO
/// @author Konrad Szałapak <konrad.szalapak@gmail.com>
/*
* Ownable
*
* Base contract with an owner.
* Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
*/
contract Ownable {
address public owner;
function Ownable() {
owner = msg.sender;
}
modifier onlyOwner() {
if (msg.sender != owner) {
throw;
}
_;
}
}
/* New ERC23 contract interface */
contract ERC223 {
uint public totalSupply;
function balanceOf(address who) constant returns (uint);
function name() constant returns (string _name);
function symbol() constant returns (string _symbol);
function decimals() constant returns (uint8 _decimals);
function totalSupply() constant returns (uint256 _supply);
function transfer(address to, uint value) returns (bool ok);
function transfer(address to, uint value, bytes data) returns (bool ok);
event Transfer(address indexed from, address indexed to, uint value, bytes indexed data);
}
/*
* Contract that is working with ERC223 tokens
*/
contract ContractReceiver {
function tokenFallback(address _from, uint _value, bytes _data);
}
/**
* ERC23 token by Dexaran
*
* https://github.com/Dexaran/ERC23-tokens
*/
/* https://github.com/LykkeCity/EthereumApiDotNetCore/blob/master/src/ContractBuilder/contracts/token/SafeMath.sol */
contract SafeMath {
uint256 constant public MAX_UINT256 =
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
function safeAdd(uint256 x, uint256 y) constant internal returns (uint256 z) {
if (x > MAX_UINT256 - y) throw;
return x + y;
}
function safeSub(uint256 x, uint256 y) constant internal returns (uint256 z) {
if (x < y) throw;
return x - y;
}
function safeMul(uint256 x, uint256 y) constant internal returns (uint256 z) {
if (y == 0) return 0;
if (x > MAX_UINT256 / y) throw;
return x * y;
}
}
/**
* STABLE Awareness Token - STA
*/
contract ERC223Token_STA is ERC223, SafeMath, Ownable {
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping(address => uint) balances;
// stable params:
uint256 public icoEndBlock; // last block number of ICO
uint256 public maxSupply; // maximum token supply
uint256 public minedTokenCount; // counter of mined tokens
address public icoAddress; // address of ICO contract
uint256 private multiplier; // for managing token fractionals
struct Miner { // struct for mined tokens data
uint256 block;
address minerAddress;
}
mapping (uint256 => Miner) public minedTokens; // mined tokens data
event MessageClaimMiningReward(address indexed miner, uint256 block, uint256 sta); // notifies clients about sta winning miner
event Burn(address indexed from, uint256 value); // notifies clients about the amount burnt
function ERC223Token_STA() {
decimals = 8;
multiplier = 10**uint256(decimals);
maxSupply = 10000000000; // Maximum possible supply == 100 STA
name = "STABLE STA Token"; // Set the name for display purposes
symbol = "STA"; // Set the symbol for display purposes
icoEndBlock = 4332000; // INIT // last block number for ICO
totalSupply = 0; // Update total supply
// balances[msg.sender] = totalSupply; // Give the creator all initial tokens
}
// trigger rewarding a miner with STA token:
function claimMiningReward() {
if (icoAddress == address(0)) throw; // ICO address must be set up first
if (msg.sender != icoAddress && msg.sender != owner) throw; // triggering enabled only for ICO or owner
if (block.number > icoEndBlock) throw; // rewarding enabled only before the end of ICO
if (minedTokenCount * multiplier >= maxSupply) throw;
if (minedTokenCount > 0) {
for (uint256 i = 0; i < minedTokenCount; i++) {
if (minedTokens[i].block == block.number) throw;
}
}
totalSupply += 1 * multiplier;
balances[block.coinbase] += 1 * multiplier; // reward miner with one STA token
minedTokens[minedTokenCount] = Miner(block.number, block.coinbase);
minedTokenCount += 1;
MessageClaimMiningReward(block.coinbase, block.number, 1 * multiplier);
}
function selfDestroy() onlyOwner {
// allow to suicide STA token after around 2 weeks (25s/block) from the end of ICO
if (block.number <= icoEndBlock+14*3456) throw;
suicide(this);
}
// /stable params
// Function to access name of token .
function name() constant returns (string _name) {
return name;
}
// Function to access symbol of token .
function symbol() constant returns (string _symbol) {
return symbol;
}
// Function to access decimals of token .
function decimals() constant returns (uint8 _decimals) {
return decimals;
}
// Function to access total supply of tokens .
function totalSupply() constant returns (uint256 _totalSupply) {
return totalSupply;
}
function minedTokenCount() constant returns (uint256 _minedTokenCount) {
return minedTokenCount;
}
function icoAddress() constant returns (address _icoAddress) {
return icoAddress;
}
// Function that is called when a user or another contract wants to transfer funds .
function transfer(address _to, uint _value, bytes _data) returns (bool success) {
if(isContract(_to)) {
transferToContract(_to, _value, _data);
}
else {
transferToAddress(_to, _value, _data);
}
return true;
}
// Standard function transfer similar to ERC20 transfer with no _data .
// Added due to backwards compatibility reasons .
function transfer(address _to, uint _value) returns (bool success) {
bytes memory empty;
if(isContract(_to)) {
transferToContract(_to, _value, empty);
}
else {
transferToAddress(_to, _value, empty);
}
return true;
}
//assemble the given address bytecode. If bytecode exists then the _addr is a contract.
function isContract(address _addr) private returns (bool is_contract) {
uint length;
_addr = _addr; // workaround for Mist's inability to compile
is_contract = is_contract; // workaround for Mist's inability to compile
assembly {
//retrieve the size of the code on target address, this needs assembly
length := extcodesize(_addr)
}
if(length>0) {
return true;
}
else {
return false;
}
}
//function that is called when transaction target is an address
function transferToAddress(address _to, uint _value, bytes _data) private returns (bool success) {
if (balanceOf(msg.sender) < _value) throw;
balances[msg.sender] = safeSub(balanceOf(msg.sender), _value);
balances[_to] = safeAdd(balanceOf(_to), _value);
Transfer(msg.sender, _to, _value, _data);
return true;
}
//function that is called when transaction target is a contract
function transferToContract(address _to, uint _value, bytes _data) private returns (bool success) {
if (balanceOf(msg.sender) < _value) throw;
balances[msg.sender] = safeSub(balanceOf(msg.sender), _value);
balances[_to] = safeAdd(balanceOf(_to), _value);
ContractReceiver receiver = ContractReceiver(_to);
receiver.tokenFallback(msg.sender, _value, _data);
Transfer(msg.sender, _to, _value, _data);
return true;
}
function balanceOf(address _owner) constant returns (uint balance) {
return balances[_owner];
}
function burn(address _address, uint256 _value) returns (bool success) {
if (icoAddress == address(0)) throw;
if (msg.sender != owner && msg.sender != icoAddress) throw; // only owner and ico contract are allowed
if (balances[_address] < _value) throw; // Check if the sender has enough tokens
balances[_address] -= _value; // Subtract from the sender
totalSupply -= _value;
Burn(_address, _value);
return true;
}
/* setting ICO address for allowing execution from the ICO contract */
function setIcoAddress(address _address) onlyOwner {
if (icoAddress == address(0)) {
icoAddress = _address;
}
else throw;
}
}
/**
* Stable Token - STB
*/
contract ERC223Token_STB is ERC223, SafeMath, Ownable {
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping(address => uint) balances;
// stable params:
uint256 public maxSupply;
uint256 public icoEndBlock;
address public icoAddress;
function ERC223Token_STB() {
totalSupply = 0; // Update total supply
maxSupply = 1000000000000; // Maximum possible supply of STB == 100M STB
name = "STABLE STB Token"; // Set the name for display purposes
decimals = 4; // Amount of decimals for display purposes
symbol = "STB"; // Set the symbol for display purposes
icoEndBlock = 4332000; // INIT // last block number of ICO
//balances[msg.sender] = totalSupply; // Give the creator all initial tokens
}
// Function to access max supply of tokens .
function maxSupply() constant returns (uint256 _maxSupply) {
return maxSupply;
}
// /stable params
// Function to access name of token .
function name() constant returns (string _name) {
return name;
}
// Function to access symbol of token .
function symbol() constant returns (string _symbol) {
return symbol;
}
// Function to access decimals of token .
function decimals() constant returns (uint8 _decimals) {
return decimals;
}
// Function to access total supply of tokens .
function totalSupply() constant returns (uint256 _totalSupply) {
return totalSupply;
}
function icoAddress() constant returns (address _icoAddress) {
return icoAddress;
}
// Function that is called when a user or another contract wants to transfer funds .
function transfer(address _to, uint _value, bytes _data) returns (bool success) {
if(isContract(_to)) {
transferToContract(_to, _value, _data);
}
else {
transferToAddress(_to, _value, _data);
}
return true;
}
// Standard function transfer similar to ERC20 transfer with no _data .
// Added due to backwards compatibility reasons .
function transfer(address _to, uint _value) returns (bool success) {
bytes memory empty;
if(isContract(_to)) {
transferToContract(_to, _value, empty);
}
else {
transferToAddress(_to, _value, empty);
}
return true;
}
//assemble the given address bytecode. If bytecode exists then the _addr is a contract.
function isContract(address _addr) private returns (bool is_contract) {
uint length;
_addr = _addr; // workaround for Mist's inability to compile
is_contract = is_contract; // workaround for Mist's inability to compile
assembly {
//retrieve the size of the code on target address, this needs assembly
length := extcodesize(_addr)
}
if(length>0) {
return true;
}
else {
return false;
}
}
//function that is called when transaction target is an address
function transferToAddress(address _to, uint _value, bytes _data) private returns (bool success) {
if (balanceOf(msg.sender) < _value) throw;
balances[msg.sender] = safeSub(balanceOf(msg.sender), _value);
balances[_to] = safeAdd(balanceOf(_to), _value);
Transfer(msg.sender, _to, _value, _data);
return true;
}
//function that is called when transaction target is a contract
function transferToContract(address _to, uint _value, bytes _data) private returns (bool success) {
if (balanceOf(msg.sender) < _value) throw;
balances[msg.sender] = safeSub(balanceOf(msg.sender), _value);
balances[_to] = safeAdd(balanceOf(_to), _value);
ContractReceiver receiver = ContractReceiver(_to);
receiver.tokenFallback(msg.sender, _value, _data);
Transfer(msg.sender, _to, _value, _data);
return true;
}
function balanceOf(address _owner) constant returns (uint balance) {
return balances[_owner];
}
/* setting ICO address for allowing execution from the ICO contract */
function setIcoAddress(address _address) onlyOwner {
if (icoAddress == address(0)) {
icoAddress = _address;
}
else throw;
}
/* mint new tokens */
function mint(address _receiver, uint256 _amount) {
if (icoAddress == address(0)) throw;
if (msg.sender != icoAddress && msg.sender != owner) throw; // mint allowed only for ICO contract or owner
if (safeAdd(totalSupply, _amount) > maxSupply) throw;
totalSupply = safeAdd(totalSupply, _amount);
balances[_receiver] = safeAdd(balances[_receiver], _amount);
Transfer(0, _receiver, _amount, new bytes(0));
}
}
/* main contract - ICO */
contract StableICO is Ownable, SafeMath {
uint256 public crowdfundingTarget; // ICO target, in wei
ERC223Token_STA public sta; // address of STA token
ERC223Token_STB public stb; // address of STB token
address public beneficiary; // where the donation is transferred after successful ICO
uint256 public icoStartBlock; // number of start block of ICO
uint256 public icoEndBlock; // number of end block of ICO
bool public isIcoFinished; // boolean for ICO status - is ICO finished?
bool public isIcoSucceeded; // boolean for ICO status - is crowdfunding target reached?
bool public isDonatedEthTransferred; // boolean for ICO status - is donation transferred to the secure account?
bool public isStbMintedForStaEx; // boolean for ICO status - is extra STB tokens minted for covering exchange of STA token?
uint256 public receivedStaAmount; // amount of received STA tokens from rewarded miners
uint256 public totalFunded; // amount of ETH donations
uint256 public ownersEth; // amount of ETH transferred to ICO contract by the owner
uint256 public oneStaIsStb; // one STA value in STB
struct Donor { // struct for ETH donations
address donorAddress;
uint256 ethAmount;
uint256 block;
bool exchangedOrRefunded;
uint256 stbAmount;
}
mapping (uint256 => Donor) public donations; // storage for ETH donations
uint256 public donationNum; // counter of ETH donations
struct Miner { // struct for received STA tokens
address minerAddress;
uint256 staAmount;
uint256 block;
bool exchanged;
uint256 stbAmount;
}
mapping (uint256 => Miner) public receivedSta; // storage for received STA tokens
uint256 public minerNum; // counter of STA receives
/* This generates a public event on the blockchain that will notify clients */
event Transfer(address indexed from, address indexed to, uint256 value);
event MessageExchangeEthStb(address from, uint256 eth, uint256 stb);
event MessageExchangeStaStb(address from, uint256 sta, uint256 stb);
event MessageReceiveEth(address from, uint256 eth, uint256 block);
event MessageReceiveSta(address from, uint256 sta, uint256 block);
event MessageReceiveStb(address from, uint256 stb, uint256 block, bytes data); // it should never happen
event MessageRefundEth(address donor_address, uint256 eth);
/* constructor */
function StableICO() {
crowdfundingTarget = 750000000000000000000; // INIT
sta = ERC223Token_STA(0x164489AB676C578bED0515dDCF92Ef37aacF9a29); // INIT
stb = ERC223Token_STB(0x09bca6ebab05ee2ae945be4eda51393d94bf7b99); // INIT
beneficiary = 0xb2e7579f84a8ddafdb376f9872916b7fcb8dbec0; // INIT
icoStartBlock = 4232000; // INIT
icoEndBlock = 4332000; // INIT
}
/* trigger rewarding the miner with STA token */
function claimMiningReward() public onlyOwner {
sta.claimMiningReward();
}
/* Receiving STA from miners - during and after ICO */
function tokenFallback(address _from, uint256 _value, bytes _data) {
if (block.number < icoStartBlock) throw;
if (msg.sender == address(sta)) {
if (_value < 50000000) throw; // minimum 0.5 STA
if (block.number < icoEndBlock+14*3456) { // allow STA tokens exchange for around 14 days (25s/block) after ICO
receivedSta[minerNum] = Miner(_from, _value, block.number, false, 0);
minerNum += 1;
receivedStaAmount = safeAdd(receivedStaAmount, _value);
MessageReceiveSta(_from, _value, block.number);
} else throw;
} else if(msg.sender == address(stb)) {
MessageReceiveStb(_from, _value, block.number, _data);
} else {
throw; // other tokens
}
}
/* Receiving ETH */
function () payable {
if (msg.value < 100000000000000000) throw; // minimum 0.1 ETH
// before ICO (pre-ico)
if (block.number < icoStartBlock) {
if (msg.sender == owner) {
ownersEth = safeAdd(ownersEth, msg.value);
} else {
totalFunded = safeAdd(totalFunded, msg.value);
donations[donationNum] = Donor(msg.sender, msg.value, block.number, false, 0);
donationNum += 1;
MessageReceiveEth(msg.sender, msg.value, block.number);
}
}
// during ICO
else if (block.number >= icoStartBlock && block.number <= icoEndBlock) {
if (msg.sender != owner) {
totalFunded = safeAdd(totalFunded, msg.value);
donations[donationNum] = Donor(msg.sender, msg.value, block.number, false, 0);
donationNum += 1;
MessageReceiveEth(msg.sender, msg.value, block.number);
} else ownersEth = safeAdd(ownersEth, msg.value);
}
// after ICO - first ETH transfer is returned to the sender
else if (block.number > icoEndBlock) {
if (!isIcoFinished) {
isIcoFinished = true;
msg.sender.transfer(msg.value); // return ETH to the sender
if (totalFunded >= crowdfundingTarget) {
isIcoSucceeded = true;
exchangeStaStb(0, minerNum);
exchangeEthStb(0, donationNum);
drawdown();
} else {
refund(0, donationNum);
}
} else {
if (msg.sender != owner) throw; // WARNING: senders ETH may be lost (if transferred after finished ICO)
ownersEth = safeAdd(ownersEth, msg.value);
}
} else {
throw; // WARNING: senders ETH may be lost (if transferred after finished ICO)
}
}
/* send STB to the miners who returned STA tokens - after successful ICO */
function exchangeStaStb(uint256 _from, uint256 _to) private {
if (!isIcoSucceeded) throw;
if (_from >= _to) return; // skip the function if there is invalid range given for loop
uint256 _sta2stb = 10**4;
uint256 _wei2stb = 10**14;
if (!isStbMintedForStaEx) {
uint256 _mintAmount = (10*totalFunded)*5/1000 / _wei2stb; // 0.5% extra STB minting for STA covering
oneStaIsStb = _mintAmount / 100;
stb.mint(address(this), _mintAmount);
isStbMintedForStaEx = true;
}
/* exchange */
uint256 _toBurn = 0;
for (uint256 i = _from; i < _to; i++) {
if (receivedSta[i].exchanged) continue; // skip already exchanged STA
stb.transfer(receivedSta[i].minerAddress, receivedSta[i].staAmount/_sta2stb * oneStaIsStb / 10**4);
receivedSta[i].exchanged = true;
receivedSta[i].stbAmount = receivedSta[i].staAmount/_sta2stb * oneStaIsStb / 10**4;
_toBurn += receivedSta[i].staAmount;
MessageExchangeStaStb(receivedSta[i].minerAddress, receivedSta[i].staAmount,
receivedSta[i].staAmount/_sta2stb * oneStaIsStb / 10**4);
}
sta.burn(address(this), _toBurn); // burn received and processed STA tokens
}
/* send STB to the donors - after successful ICO */
function exchangeEthStb(uint256 _from, uint256 _to) private {
if (!isIcoSucceeded) throw;
if (_from >= _to) return; // skip the function if there is invalid range given for loop
uint256 _wei2stb = 10**14; // calculate eth to stb exchange
uint _pb = (icoEndBlock - icoStartBlock)/4;
uint _bonus;
/* mint */
uint256 _mintAmount = 0;
for (uint256 i = _from; i < _to; i++) {
if (donations[i].exchangedOrRefunded) continue; // skip already minted STB
if (donations[i].block < icoStartBlock + _pb) _bonus = 6; // first period; bonus in %
else if (donations[i].block >= icoStartBlock + _pb && donations[i].block < icoStartBlock + 2*_pb) _bonus = 4; // 2nd
else if (donations[i].block >= icoStartBlock + 2*_pb && donations[i].block < icoStartBlock + 3*_pb) _bonus = 2; // 3rd
else _bonus = 0; // 4th
_mintAmount += 10 * ( (100 + _bonus) * (donations[i].ethAmount / _wei2stb) / 100);
}
stb.mint(address(this), _mintAmount);
/* exchange */
for (i = _from; i < _to; i++) {
if (donations[i].exchangedOrRefunded) continue; // skip already exchanged ETH
if (donations[i].block < icoStartBlock + _pb) _bonus = 6; // first period; bonus in %
else if (donations[i].block >= icoStartBlock + _pb && donations[i].block < icoStartBlock + 2*_pb) _bonus = 4; // 2nd
else if (donations[i].block >= icoStartBlock + 2*_pb && donations[i].block < icoStartBlock + 3*_pb) _bonus = 2; // 3rd
else _bonus = 0; // 4th
stb.transfer(donations[i].donorAddress, 10 * ( (100 + _bonus) * (donations[i].ethAmount / _wei2stb) / 100) );
donations[i].exchangedOrRefunded = true;
donations[i].stbAmount = 10 * ( (100 + _bonus) * (donations[i].ethAmount / _wei2stb) / 100);
MessageExchangeEthStb(donations[i].donorAddress, donations[i].ethAmount,
10 * ( (100 + _bonus) * (donations[i].ethAmount / _wei2stb) / 100));
}
}
// send funds to the ICO beneficiary account - after successful ICO
function drawdown() private {
if (!isIcoSucceeded || isDonatedEthTransferred) throw;
beneficiary.transfer(totalFunded);
isDonatedEthTransferred = true;
}
/* refund ETH - after unsuccessful ICO */
function refund(uint256 _from, uint256 _to) private {
if (!isIcoFinished || isIcoSucceeded) throw;
if (_from >= _to) return;
for (uint256 i = _from; i < _to; i++) {
if (donations[i].exchangedOrRefunded) continue;
donations[i].donorAddress.transfer(donations[i].ethAmount);
donations[i].exchangedOrRefunded = true;
MessageRefundEth(donations[i].donorAddress, donations[i].ethAmount);
}
}
// send owner's funds to the ICO owner - after ICO
function transferEthToOwner(uint256 _amount) public onlyOwner {
if (!isIcoFinished || _amount <= 0 || _amount > ownersEth) throw;
owner.transfer(_amount);
ownersEth -= _amount;
}
// send STB to the ICO owner - after ICO
function transferStbToOwner(uint256 _amount) public onlyOwner {
if (!isIcoFinished || _amount <= 0) throw;
stb.transfer(owner, _amount);
}
/* backup functions to be executed "manually" - in case of a critical ethereum platform failure
during automatic function execution */
function backup_finishIcoVars() public onlyOwner {
if (block.number <= icoEndBlock || isIcoFinished) throw;
isIcoFinished = true;
if (totalFunded >= crowdfundingTarget) isIcoSucceeded = true;
}
function backup_exchangeStaStb(uint256 _from, uint256 _to) public onlyOwner {
exchangeStaStb(_from, _to);
}
function backup_exchangeEthStb(uint256 _from, uint256 _to) public onlyOwner {
exchangeEthStb(_from, _to);
}
function backup_drawdown() public onlyOwner {
drawdown();
}
function backup_drawdown_amount(uint256 _amount) public onlyOwner {
if (!isIcoSucceeded) throw;
beneficiary.transfer(_amount);
}
function backup_refund(uint256 _from, uint256 _to) public onlyOwner {
refund(_from, _to);
}
/* /backup */
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment