Skip to content

Instantly share code, notes, and snippets.

@simform-solutions
Created December 6, 2017 13:07
Show Gist options
  • Save simform-solutions/45c709ad265c8f9e641bc8a8e3be5872 to your computer and use it in GitHub Desktop.
Save simform-solutions/45c709ad265c8f9e641bc8a8e3be5872 to your computer and use it in GitHub Desktop.
pragma solidity ^0.4.15;
/**
* Overflow aware uint math functions.
*/
contract SafeMath {
//internals
function safeMul(uint a, uint b) internal pure returns (uint) {
uint c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function safeSub(uint a, uint b) internal pure returns (uint) {
assert(b <= a);
return a - b;
}
function safeAdd(uint a, uint b) internal pure returns (uint) {
uint c = a + b;
assert(c>=a && c>=b);
return c;
}
}
/**
* ERC 20 token
*
* https://github.com/ethereum/EIPs/issues/20
*/
contract Token {
/// @return total amount of tokens
function totalSupply() public constant returns (uint256 supply);
/// @param _owner The address from which the balance will be retrieved
/// @return The balance
function balanceOf(address _owner) public constant returns (uint256 balance);
/// @notice send `_value` token to `_to` from `msg.sender`
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transfer(address _to, uint256 _value) public returns (bool success);
/// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
/// @param _from The address of the sender
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
/// @notice `msg.sender` approves `_addr` to spend `_value` tokens
/// @param _spender The address of the account able to transfer the tokens
/// @param _value The amount of wei to be approved for transfer
/// @return Whether the approval was successful or not
function approve(address _spender, uint256 _value) public returns (bool success);
/// @param _owner The address of the account owning tokens
/// @param _spender The address of the account able to transfer the tokens
/// @return Amount of remaining tokens allowed to spent
function allowance(address _owner, address _spender) public constant returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
/**
* ERC 20 token
*
* https://github.com/ethereum/EIPs/issues/20
*/
contract StandardToken is Token {
mapping(address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
uint256 public _totalSupply;
/**
* Reviewed:
* - Interger overflow = OK, checked
*/
function transfer(address _to, uint256 _value) public returns (bool success) {
//Default assumes totalSupply can't be over max (2^256 - 1).
//If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
//Replace the if with this one instead.
if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
//if (balances[msg.sender] >= _value && _value > 0) {
balances[msg.sender] -= _value;
balances[_to] += _value;
Transfer(msg.sender, _to, _value);
return true;
} else { return false; }
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
//same as above. Replace this line with the following if you want to protect against wrapping uints.
if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
//if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) {
balances[_to] += _value;
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
Transfer(_from, _to, _value);
return true;
} else { return false; }
}
function balanceOf(address _owner) public constant returns (uint256 balance) {
return balances[_owner];
}
function approve(address _spender, uint256 _value) public returns (bool success) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) public constant returns (uint256 remaining) {
return allowed[_owner][_spender];
}
}
/**
* Security criteria evaluated against http://ethereum.stackexchange.com/questions/8551/methodological-security-review-of-a-smart-contract
*/
contract MyNewToken is StandardToken, SafeMath {
string public name = "My New Token";
string public symbol = "Myn";
uint public decimals = 18;
uint public startBlock; //crowdsale start block (set in constructor)
uint public endBlock; //crowdsale end block (set in constructor)
// Initial founder address (set in constructor)
// All deposited ETH will be instantly forwarded to this address.
// Address is a multisig wallet.
address public founder = 0x0;
// signer address (for clickwrap agreement)
// see function() {} for comments
address public signer = 0x0;
uint public etherCap = 100 * 10**18; //max amount raised during crowdsale (100 USD worth of ether will be measured with market price at beginning of the crowdsale)
uint public transferLockup = 370285; //transfers are locked for this many blocks after endBlock (assuming 14 second blocks, this is 2 months)
uint public founderLockup = 2252571; //founder allocation cannot be created until this many blocks after endBlock (assuming 14 second blocks, this is 1 year)
uint public bountyAllocation = 25 * 10**18; //25 tokens allocated post-crowdsale for the bounty fund
uint public ecosystemAllocation = 5 * 10**16; //5% of token supply allocated post-crowdsale for the ecosystem fund
uint public founderAllocation = 10 * 10**16; //10% of token supply allocated post-crowdsale for the founder allocation
bool public bountyAllocated = false; //this will change to true when the bounty fund is allocated
bool public ecosystemAllocated = false; //this will change to true when the ecosystem fund is allocated
bool public founderAllocated = false; //this will change to true when the founder fund is allocated
uint public presaleTokenSupply = 0; //this will keep track of the token supply created during the crowdsale
uint public presaleEtherRaised = 0; //this will keep track of the Ether raised during the crowdsale
bool public halted = false; //the founder address can set this to true to halt the crowdsale due to emergency
event Buy(address indexed sender, uint eth, uint fbt);
event Withdraw(address indexed sender, address to, uint eth);
event AllocateFounderTokens(address indexed sender);
event AllocateBountyAndEcosystemTokens(address indexed sender);
function MyNewToken(address founderInput, address signerInput, uint startBlockInput, uint endBlockInput) public {
founder = founderInput;
signer = signerInput;
startBlock = startBlockInput;
endBlock = endBlockInput;
}
/**
* Security review
*
* - Integer overflow: does not apply, blocknumber can't grow that high
* - Division is the last operation and constant, should not cause issues
*/
function price() public constant returns(uint) {
if (block.number>=startBlock && block.number<startBlock+250) return 170; //power hour
if (block.number<startBlock || block.number>endBlock) return 100; //default price
return 100 + 4*(endBlock - block.number)/(endBlock - startBlock + 1)*67/4; //crowdsale price
}
// price() exposed for unit tests
function testPrice(uint blockNumber) public constant returns(uint) {
if (blockNumber>=startBlock && blockNumber<startBlock+250) return 170; //power hour
if (blockNumber<startBlock || blockNumber>endBlock) return 100; //default price
return 100 + 4*(endBlock - blockNumber)/(endBlock - startBlock + 1)*67/4; //crowdsale price
}
// Buy entry point
function buy(uint8 v, bytes32 r, bytes32 s) public{
buyRecipient(msg.sender, v, r, s);
}
/**
* Main token buy function.
*
* Buy for the sender itself or buy on the behalf of somebody else (third party address).
*
* Security review
*
* - Integer math: ok - using SafeMath
*
* - halt flag added - ok
*
* Applicable tests:
*
* - Test halting, buying, and failing
* - Test buying on behalf of a recipient
* - Test buy
* - Test unhalting, buying, and succeeding
* - Test buying after the sale ends
*
*/
function buyRecipient(address recipient, uint8 v, bytes32 r, bytes32 s) public payable{
bytes32 hash = sha256(msg.sender);
require (ecrecover(hash,v,r,s) == signer);
require (block.number>startBlock || block.number<endBlock || safeAdd(presaleEtherRaised,msg.value)<etherCap || !halted);
uint tokens = safeMul(msg.value, price());
balances[recipient] = safeAdd(balances[recipient], tokens);
_totalSupply = safeAdd(_totalSupply, tokens);
presaleEtherRaised = safeAdd(presaleEtherRaised, msg.value);
// TODO: Is there a pitfall of forwarding message value like this
// TODO: Different address for founder deposits and founder operations (halt, unhalt)
// as founder opeations might be easier to perform from normal geth account
require (founder.send(msg.value)); //immediately send Ether to founder address
Buy(recipient, msg.value, tokens);
}
/**
* Set up founder address token balance.
*
* allocateBountyAndEcosystemTokens() must be calld first.
*
* Security review
*
* - Integer math: ok - only called once with fixed parameters
*
* Applicable tests:
*
* - Test bounty and ecosystem allocation
* - Test bounty and ecosystem allocation twice
*
*/
function allocateFounderTokens() public{
require (msg.sender==founder);
require (block.number > endBlock + founderLockup);
require (!founderAllocated);
require (bountyAllocated || ecosystemAllocated);
balances[founder] = safeAdd(balances[founder], presaleTokenSupply * founderAllocation / (1 ether));
_totalSupply = safeAdd(_totalSupply, presaleTokenSupply * founderAllocation / (1 ether));
founderAllocated = true;
AllocateFounderTokens(msg.sender);
}
/**
* Set up founder address token balance.
*
* Set up bounty pool.
*
* Security review
*
* - Integer math: ok - only called once with fixed parameters
*
* Applicable tests:
*
* - Test founder token allocation too early
* - Test founder token allocation on time
* - Test founder token allocation twice
*
*/
function allocateBountyAndEcosystemTokens() public{
require (msg.sender==founder);
require (block.number > endBlock);
require (!bountyAllocated || !ecosystemAllocated);
presaleTokenSupply = _totalSupply;
balances[founder] = safeAdd(balances[founder], presaleTokenSupply * ecosystemAllocation / (1 ether));
_totalSupply = safeAdd(_totalSupply, presaleTokenSupply * ecosystemAllocation / (1 ether));
balances[founder] = safeAdd(balances[founder], bountyAllocation);
_totalSupply = safeAdd(_totalSupply, bountyAllocation);
bountyAllocated = true;
ecosystemAllocated = true;
AllocateBountyAndEcosystemTokens(msg.sender);
}
/**
* Emergency Stop ICO.
*
* Applicable tests:
*
* - Test unhalting, buying, and succeeding
*/
function halt() public{
require (msg.sender==founder);
halted = true;
}
function unhalt() public{
require(msg.sender==founder);
halted = false;
}
/**
* Change founder address (where ICO ETH is being forwarded).
*
* Applicable tests:
*
* - Test founder change by hacker
* - Test founder change
* - Test founder token allocation twice
*
function changeFounder(address newFounder) {
if (msg.sender!=founder) throw;
founder = newFounder;
}
/**
* ERC 20 Standard Token interface transfer function
*
* Prevent transfers until freeze period is over.
*
* Applicable tests:
*
* - Test restricted early transfer
* - Test transfer after restricted period
*/
function transfer(address _to, uint256 _value) public returns (bool success) {
require (block.number > endBlock + transferLockup && msg.sender==founder);
return super.transfer(_to, _value);
}
/**
* ERC 20 Standard Token interface transfer function
*
* Prevent transfers until freeze period is over.
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require (block.number > endBlock + transferLockup && msg.sender==founder);
return super.transferFrom(_from, _to, _value);
}
function totalSupply() public constant returns(uint _supply){
return _totalSupply;
}
/**
* Do not allow direct deposits.
*
* All crowdsale depositors must have read the legal agreement.
* This is confirmed by having them signing the terms of service on the website.
* The give their crowdsale Ethereum source address on the website.
* Website signs this address using crowdsale private key (different from founders key).
* buy() takes this signature as input and rejects all deposits that do not have
* signature you receive after reading terms of service.
*
*/
function() internal{
assert(false);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment