Skip to content

Commit

Permalink
1. Adding full Test Suite
Browse files Browse the repository at this point in the history
2. Made changes post Token Review
   - Made Token stoppable via Wallet instead of pre-image
   - Typo fixes
3. Added logic to converge with marketing materials
   - Crowdsale Limit by Token created
   - Added WithdrawReserve to mint remaining tokens post crowdsale
  • Loading branch information
kryptoslab committed Jun 10, 2017
1 parent 48885eb commit 7ddf17e
Show file tree
Hide file tree
Showing 9 changed files with 2,781 additions and 35 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
test/*
build/*
node_modules/*
migrations/*
Expand Down
76 changes: 57 additions & 19 deletions contracts/Token.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ pragma solidity ^0.4.11;
/*
This FYN token contract is derived from the vSlice ICO contract, based on the ERC20 token contract.
Additional functionality has been integrated:
* the function mintTokens(), which makes use of the currentSwapRate() and safeToAdd() helpers
* the function stopToken(uint256 stopKey), which in an emergency, will trigger a complete and irrecoverable shutdown of the token
* the function mintTokens() only callable from wallet, which makes use of the currentSwapRate() and safeToAdd() helpers
* the function mintReserve() only callable from wallet, which at the end of the crowdsale will allow the owners to claim the unsold tokens
* the function stopToken() only callable from wallet, which in an emergency, will trigger a complete and irrecoverable shutdown of the token
* Contract tokens are locked when created, and no tokens including pre-mine can be moved until the crowdsale is over.
*/


// ERC20 Token Standard Interface
// https://github.com/ethereum/EIPs/issues/20
contract ERC20 {
function totalSupply() constant returns (uint);
function balanceOf(address who) constant returns (uint);
Expand All @@ -22,15 +26,18 @@ contract ERC20 {

contract Token is ERC20 {

string public constant name = "FundYourselfNow Token";
string public constant symbol = "FYN";
uint8 public constant decimals = 18; // 18 is the most common number of decimal places
uint256 public tokenCap = 12500000e18; // 12.5 million FYN cap

address public walletAddress;
uint256 public creationTime;
bool public transferStop;

mapping( address => uint ) _balances;
mapping( address => mapping( address => uint ) ) _approvals;
uint _supply;
address public walletAddress;
bool transferStop;
uint256 public creationTime;
string public constant name = "FundYourselNow Token";
string public constant symbol = "FYN";
uint8 public constant decimals = 18; // 18 is the most common number of decimal places

event TokenMint(address newTokenHolder, uint amountOfTokens);
event TokenSwapOver();
Expand All @@ -41,13 +48,13 @@ contract Token is ERC20 {
_;
}


// Check if transfer should stop
modifier checkTransferStop {
if (transferStop == true) throw;
_;
}


/**
*
* Fix for the ERC20 short address attack
Expand Down Expand Up @@ -90,11 +97,18 @@ contract Token is ERC20 {
return (_b == 0 || ((_a * _b) / _b) == _a);
}

// A helper to notify if underflow occurs for subtraction
function safeToSub(uint a, uint b) private constant returns (bool) {
return (a >= b);
}


function transfer( address to, uint value)
checkTransferStop
onlyPayloadSize(2 * 32)
returns (bool ok) {

if (to == walletAddress) throw; // Reject transfers to wallet (wallet cannot interact with token contract)
if( _balances[msg.sender] < value ) {
throw;
}
Expand All @@ -111,6 +125,9 @@ contract Token is ERC20 {
function transferFrom( address from, address to, uint value)
checkTransferStop
returns (bool ok) {

if (to == walletAddress) throw; // Reject transfers to wallet (wallet cannot interact with token contract)

// if you don't have enough balance, throw
if( _balances[from] < value ) {
throw;
Expand Down Expand Up @@ -157,10 +174,17 @@ contract Token is ERC20 {
// The function currentSwapRate() returns the current exchange rate
// between FYN tokens and Ether during the token swap period
function currentSwapRate() constant returns(uint) {
if (creationTime + 4 weeks > now) {
uint presalePeriod = 3 days;
if (creationTime + presalePeriod > now) {
return 140;
}
else if (creationTime + presalePeriod + 3 weeks > now) {
return 120;
}
else if (creationTime + 8 weeks + 3 days + 3 hours > now) {
else if (creationTime + presalePeriod + 6 weeks + 6 days + 3 hours + 1 days > now) {
// 1 day buffer to allow one final transaction from anyone to close everything
// otherwise wallet will receive ether but send 0 tokens
// we cannot throw as we will lose the state change to start swappability of tokens
return 100;
}
else {
Expand All @@ -181,12 +205,30 @@ contract Token is ERC20 {
if(!safeToAdd(_balances[newTokenHolder],tokensAmount )) throw;
if(!safeToAdd(_supply,tokensAmount)) throw;

if ((_supply + tokensAmount) > tokenCap) throw;

_balances[newTokenHolder] += tokensAmount;
_supply += tokensAmount;

TokenMint(newTokenHolder, tokensAmount);
}

function mintReserve(address beneficiary)
external
onlyFromWallet {
if (tokenCap <= _supply) throw;
if(!safeToSub(tokenCap,_supply)) throw;
uint tokensAmount = tokenCap - _supply;

if(!safeToAdd(_balances[beneficiary], tokensAmount )) throw;
if(!safeToAdd(_supply,tokensAmount)) throw;

_balances[beneficiary] += tokensAmount;
_supply += tokensAmount;

TokenMint(beneficiary, tokensAmount);
}

// The function disableTokenSwapLock() is called by the wallet
// contract once the token swap has reached its end conditions
function disableTokenSwapLock()
Expand All @@ -196,13 +238,9 @@ contract Token is ERC20 {
TokenSwapOver();
}

// Token can only be stopped with a secret 256-bits key, as an emergency precaution.
// This is a last resort, irreversible action.
// Once activated, a new token contract will need to be created, mirroring the current token holdings.
function stopToken(uint256 preimage) {
if (sha3(preimage) == 0x7b3fd1d8651e004db37810765677debafacf72152495c08e2b4aa7fad6552300) {
transferStop = true;
EmergencyStopActivated();
}
// Once activated, a new token contract will need to be created, mirroring the current token holdings.
function stopToken() onlyFromWallet {
transferStop = true;
EmergencyStopActivated();
}
}
77 changes: 62 additions & 15 deletions contracts/Wallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ all the changes:
- Functions for starting and stopping the tokenswap
- A set-only-once function for the token contract
- buyTokens(), which calls mintTokens() in the token contract
- Modifiers for enforcing tokenswap time limits and max ether cap
- Modifiers for enforcing tokenswap time limits, max ether cap, and max token cap
- withdrawEther(), for withdrawing unsold tokens after time cap
* the wallet fallback function calls the buyTokens function
* the wallet contract cannot selfdestruct during the tokenswap
*/
Expand Down Expand Up @@ -328,18 +329,35 @@ contract multisig {
contract tokenswap is multisig, multiowned {
Token public tokenCtr;
bool public tokenSwap;
uint public constant SWAP_LENGTH = 8 weeks + 3 days + 3 hours;
uint public constant MAX_ETH = 80000 ether;
uint public constant PRESALE_LENGTH = 3 days;
uint public constant SWAP_LENGTH = PRESALE_LENGTH + 6 weeks + 6 days + 3 hours;
uint public constant MAX_ETH = 75000 ether; // Hard cap, capped otherwise by total tokens sold (max 7.5M FYN)
uint public amountRaised;

modifier isUnderPresaleMinimum {
if (tokenCtr.creationTime() + PRESALE_LENGTH > now) {
if (msg.value < 20 ether) throw;
}
_;
}

modifier isZeroValue {
if (msg.value == 0) throw;
_;
}

modifier isOverCap {
if (amountRaised + msg.value > MAX_ETH) throw;
if (amountRaised + msg.value > MAX_ETH) throw;
_;
}

modifier isOverTokenCap {
if (!safeToMultiply(tokenCtr.currentSwapRate(), msg.value)) throw;
uint tokensAmount = tokenCtr.currentSwapRate() * msg.value;
if(!safeToAdd(tokenCtr.totalSupply(),tokensAmount)) throw;
if (tokenCtr.totalSupply() + tokensAmount > tokenCtr.tokenCap()) throw;
_;

}

modifier isSwapStopped {
Expand All @@ -348,24 +366,37 @@ contract tokenswap is multisig, multiowned {
}

modifier areConditionsSatisfied {
// End token swap if sale period ended
if (tokenCtr.creationTime() + SWAP_LENGTH < now) {
_;
// End token swap if sale period ended
// We can't throw to reverse the amount sent in or we will lose state
// , so we will accept it even though if it is after crowdsale
if (tokenCtr.creationTime() + SWAP_LENGTH < now) {
tokenCtr.disableTokenSwapLock();
tokenSwap = false;
}
else {
_;
// Check if cap has been reached in this tx
if (amountRaised == MAX_ETH) {
tokenCtr.disableTokenSwapLock();
tokenSwap = false;
}
// Check if cap has been reached in this tx
if (amountRaised == MAX_ETH) {
tokenCtr.disableTokenSwapLock();
tokenSwap = false;
}

// Check if token cap has been reach in this tx
if (tokenCtr.totalSupply() == tokenCtr.tokenCap()) {
tokenCtr.disableTokenSwapLock();
tokenSwap = false;
}
}

function safeToAdd(uint a, uint b) internal returns (bool) {
// A helper to notify if overflow occurs for addition
function safeToAdd(uint a, uint b) private constant returns (bool) {
return (a + b >= a && a + b >= b);
}

// A helper to notify if overflow occurs for multiplication
function safeToMultiply(uint _a, uint _b) private constant returns (bool) {
return (_b == 0 || ((_a * _b) / _b) == _a);
}


function startTokenSwap() onlyowner {
tokenSwap = true;
Expand All @@ -385,15 +416,23 @@ contract tokenswap is multisig, multiowned {

function buyTokens(address _beneficiary)
payable
isUnderPresaleMinimum
isZeroValue
isOverCap
isOverTokenCap
isSwapStopped
areConditionsSatisfied {
Deposit(msg.sender, msg.value);
tokenCtr.mintTokens(_beneficiary, msg.value);
if (!safeToAdd(amountRaised, msg.value)) throw;
amountRaised += msg.value;
}

function withdrawReserve(address _beneficiary) onlyowner {
if (tokenCtr.creationTime() + SWAP_LENGTH < now) {
tokenCtr.mintReserve(_beneficiary);
}
}
}

// usage:
Expand All @@ -420,11 +459,19 @@ contract Wallet is multisig, multiowned, daylimit, tokenswap {

// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
//ensure owners can't prematurely stop token sale
// ensure owners can't prematurely stop token sale
if (tokenSwap) throw;
// ensure owners can't kill wallet without stopping token
// otherwise token can never be stopped
if (tokenCtr.transferStop() == false) throw;
suicide(_to);
}

// Activates Emergency Stop for Token
function stopToken() onlymanyowners(sha3(msg.data)) external {
tokenCtr.stopToken();
}

// gets called when no other function matches
function()
payable {
Expand Down
Loading

0 comments on commit 7ddf17e

Please sign in to comment.