Skip to content

Instantly share code, notes, and snippets.

@CJentzsch
Created February 13, 2017 08:49
Show Gist options
  • Save CJentzsch/45ccb6641cdbeba39bdb01a493f9a510 to your computer and use it in GitHub Desktop.
Save CJentzsch/45ccb6641cdbeba39bdb01a493f9a510 to your computer and use it in GitHub Desktop.
/*
The MIT License (MIT)
Copyright (c) 2016 DFINITY Stiftung
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* @title: The DFINITY Stiftung donation contract (FDC).
* @author: Timo Hanke <timo.t.hanke@gmail.com>
*
* This contract
* - accepts on-chain donations for the foundation in ether
* - tracks on-chain and off-chain donations made to the foundation
* - assigns unrestricted tokens to addresses provided by donors
* - assigns restricted tokens to DFINITY Stiftung and early contributors
*
* On-chain donations are received in ether are converted to Swiss francs (CHF).
* Off-chain donations are received and recorded directly in Swiss francs.
* Tokens are assigned at a rate of 10 tokens per CHF.
*
* There are two types of tokens initially. Unrestricted tokens are assigned to
* donors and restricted tokens are assigned to DFINITY Stiftung and early
* contributors. Restricted tokens are converted to unrestricted tokens in the
* finalization phase, after which only unrestricted tokens exist.
*
* After the finalization phase, tokens assigned to DFINITY Stiftung and early
* contributors will make up a pre-defined share of all tokens. This is achieved
* through burning excess restricted tokens before their restriction is removed.
*/
pragma solidity ^0.4.6;
/*
The MIT License (MIT)
Copyright (c) 2016 DFINITY Stiftung
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* @title: A contract that tracks numbers of tokens assigned to addresses.
* @author: Timo Hanke <timo.t.hanke@gmail.com>
*
* Optionally, assignments can be chosen to be of "restricted type".
* Being "restricted" means that the token assignment may later be partially
* reverted (or the tokens "burned") by the contract.
*
* After all token assignments are completed the contract
* - burns some restricted tokens
* - releases the restriction on the remaining tokens
* The percentage of tokens that burned out of each assignment of restricted
* tokens is calculated to achieve the following condition:
* - the remaining formerly restricted tokens combined have a pre-configured
* share (percentage) among all remaining tokens.
*
* Once the conversion process has started the contract enters a state in which
* no more assignments can be made.
*/
pragma solidity ^0.4.6;
contract TokenTracker {
// Share of formerly restricted tokens among all tokens in percent
uint public restrictedShare;
// Mapping from address to number of tokens assigned to the address
mapping(address => uint) public tokens;
// Mapping from address to number of tokens assigned to the address that
// underly a restriction
mapping(address => uint) public restrictions;
// Total number of (un)restricted tokens currently in existence
uint public totalRestrictedTokens;
uint public totalUnrestrictedTokens;
// Total number of individual assignment calls have been for (un)restricted
// tokens
uint public totalRestrictedAssignments;
uint public totalUnrestrictedAssignments;
// State flag. Assignments can only be made if false.
// Starting the conversion (burn) process irreversibly sets this to true.
bool public assignmentsClosed = false;
// The multiplier (defined by nominator and denominator) that defines the
// fraction of all restricted tokens to be burned.
// This is computed after assignments have ended and before the conversion
// process starts.
uint public burnMultDen;
uint public burnMultNom;
function TokenTracker(uint _restrictedShare) {
// Throw if restricted share >= 100
if (_restrictedShare >= 100) { throw; }
restrictedShare = _restrictedShare;
}
/**
* PUBLIC functions
*
* - isUnrestricted (getter)
* - multFracCeiling (library function)
* - isRegistered(addr) (getter)
*/
/**
* Return true iff the assignments are closed and there are no restricted
* tokens left
*/
function isUnrestricted() constant returns (bool) {
return (assignmentsClosed && totalRestrictedTokens == 0);
}
/**
* Return the ceiling of (x*a)/b
*
* Edge cases:
* a = 0: return 0
* b = 0, a != 0: error (solidity throws on division by 0)
*/
function multFracCeiling(uint x, uint a, uint b) returns (uint) {
// Catch the case a = 0
if (a == 0) { return 0; }
// Rounding up is the same as adding 1-epsilon and rounding down.
// 1-epsilon is modeled as (b-1)/b below.
return (x * a + (b - 1)) / b;
}
/**
* Return true iff the address has tokens assigned (resp. restricted tokens)
*/
function isRegistered(address addr, bool restricted) constant returns (bool) {
if (restricted) {
return (restrictions[addr] > 0);
} else {
return (tokens[addr] > 0);
}
}
/**
* INTERNAL functions
*
* - assign
* - closeAssignments
* - unrestrict
*/
/**
* Assign (un)restricted tokens to given address
*/
function assign(address addr, uint tokenAmount, bool restricted) internal {
// Throw if assignments have been closed
if (assignmentsClosed) { throw; }
// Assign tokens
tokens[addr] += tokenAmount;
// Record restrictions and update total counters
if (restricted) {
totalRestrictedTokens += tokenAmount;
totalRestrictedAssignments += 1;
restrictions[addr] += tokenAmount;
} else {
totalUnrestrictedTokens += tokenAmount;
totalUnrestrictedAssignments += 1;
}
}
/**
* Close future assignments.
*
* This is irreversible and closes all future assignments.
* The function can only be called once.
*
* A call triggers the calculation of what fraction of restricted tokens
* should be burned by subsequent calls to the unrestrict() function.
* The result of this calculation is a multiplication factor whose nominator
* and denominator are stored in the contract variables burnMultNom,
* burnMultDen.
*/
function closeAssignmentsIfOpen() internal {
// Return if assignments are not open
if (assignmentsClosed) { return; }
// Set the state to "closed"
assignmentsClosed = true;
/*
* Calculate the total number of tokens that should remain after
* conversion. This is based on the total number of unrestricted tokens
* assigned so far and the pre-configured share that the remaining
* formerly restricted tokens should have.
*/
uint totalTokensTarget = (totalUnrestrictedTokens * 100) /
(100 - restrictedShare);
// The total number of tokens in existence now.
uint totalTokensExisting = totalRestrictedTokens + totalUnrestrictedTokens;
/*
* The total number of tokens that need to be burned to bring the existing
* number down to the target number. If the existing number is lower than
* the target then we won't burn anything.
*/
uint totalBurn = 0;
if (totalTokensExisting > totalTokensTarget) {
totalBurn = totalTokensExisting - totalTokensTarget;
}
// The fraction of restricted tokens to be burned (by nominator and
// denominator).
burnMultNom = totalBurn;
burnMultDen = totalRestrictedTokens;
/*
* For verifying the correctness of the above calculation it may help to
* note the following.
* Given 0 <= restrictedShare < 100, we have:
* - totalTokensTarget >= totalUnrestrictedTokens
* - totalTokensExisting <= totalRestrictedTokens + totalTokensTarget
* - totalBurn <= totalRestrictedTokens
* - burnMultNom <= burnMultDen
* Also note that burnMultDen = 0 means totalRestrictedTokens = 0, in which
* burnMultNom = 0 as well.
*/
}
/**
* Unrestrict (convert) all restricted tokens assigned to the given address
*
* This function can only be called after assignments have been closed via
* closeAssignments().
* The return value is the number of restricted tokens that were burned in
* the conversion.
*/
function unrestrict(address addr) internal returns (uint) {
// Throw is assignments are not yet closed
if (!assignmentsClosed) { throw; }
// The balance of restricted tokens for the given address
uint restrictionsForAddr = restrictions[addr];
// Throw if there are none
if (restrictionsForAddr == 0) { throw; }
// Apply the burn multiplier to the balance of restricted tokens
// The result is the ceiling of the value:
// (restrictionForAddr * burnMultNom) / burnMultDen
uint burn = multFracCeiling(restrictionsForAddr, burnMultNom, burnMultDen);
// Remove the tokens to be burned from the address's balance
tokens[addr] -= burn;
// Delete record of restrictions
delete restrictions[addr];
// Update the counters for total (un)restricted tokens
totalRestrictedTokens -= restrictionsForAddr;
totalUnrestrictedTokens += restrictionsForAddr - burn;
return burn;
}
}
/*
The MIT License (MIT)
Copyright (c) 2016 DFINITY Stiftung
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
* @title: Contract that advances through multiple configurable phases over time
* @author: Timo Hanke <timo.t.hanke@gmail.com>
*
* Phases are defined by their transition times. The moment one phase ends the
* next one starts. Each time belongs to exactly one phase.
*
* The contract allows a limited set of changes to be applied to the phase
* transitions while the contract is active. As a matter of principle, changes
* are prohibited from effecting the past. They may only ever affect future
* phase transitions.
*
* The permitted changes are:
* - add a new phase after the last one
* - end the current phase right now and transition to the next phase
* immediately
* - delay the start of a future phase (thereby pushing out all subsequent
* phases by an equal amount of time)
* - define a maximum delay for a specified phase
*/
pragma solidity ^0.4.6;
contract Phased {
/**
* Array of transition times defining the phases
*
* phaseEndTime[i] is the time when phase i has just ended.
* Phase i is defined as the following time interval:
* [ phaseEndTime[i-1], * phaseEndTime[i] )
*/
uint[] public phaseEndTime;
/**
* Number of phase transitions N = phaseEndTime.length
*
* There are N+1 phases, numbered 0,..,N.
* The first phase has no start and the last phase has no end.
*/
uint public N;
/**
* Maximum delay for phase transitions
*
* maxDelay[i] is the maximum amount of time by which the transition
* phaseEndTime[i] can be delayed.
*/
mapping(uint => uint) public maxDelay;
/*
* The contract has no constructor.
* The contract initialized itself with no phase transitions (N = 0) and one
* phase (N+1=1).
*
* There are two PUBLIC functions (getters):
* - getPhaseAtTime
* - isPhase
* - getPhaseStartTime
*
* Note that both functions are guaranteed to return the same value when
* called twice with the same argument (but at different times).
*/
/**
* Return the number of the phase to which the given time belongs.
*
* Return value i means phaseEndTime[i-1] <= time < phaseEndTime[i].
* The given time must not be in the future (because future phase numbers may
* still be subject to change).
*/
function getPhaseAtTime(uint time) constant returns (uint n) {
// Throw if time is in the future
if (time > now) { throw; }
// Loop until we have found the "active" phase
while (n < N && phaseEndTime[n] <= time) {
n++;
}
}
/**
* Return true if the given time belongs to the given phase.
*
* Returns the logical equivalent of the expression
* (phaseEndTime[i-1] <= time < phaseEndTime[i]).
*
* The given time must not be in the future (because future phase numbers may
* still be subject to change).
*/
function isPhase(uint time, uint n) constant returns (bool) {
// Throw if time is in the future
if (time > now) { throw; }
// Throw if index is out-of-range
if (n >= N) { throw; }
// Condition 1
if (n > 0 && phaseEndTime[n-1] > time) { return false; }
// Condition 2
if (n < N && time >= phaseEndTime[n]) { return false; }
return true;
}
/**
* Return the start time of the given phase.
*
* This function is provided for convenience.
* The given phase number must not be 0, as the first phase has no start time.
* If calling for a future phase number the caller must be aware that future
* phase times can be subject to change.
*/
function getPhaseStartTime(uint n) constant returns (uint) {
// Throw if phase is the first phase
if (n == 0) { throw; }
return phaseEndTime[n-1];
}
/*
* There are 4 INTERNAL functions:
* 1. addPhase
* 2. setMaxDelay
* 3. delayPhaseEndBy
* 4. endCurrentPhaseIn
*
* This contract does not implement access control to these function, so
* they are made internal.
*/
/**
* 1. Add a phase after the last phase.
*
* The argument is the new endTime of the phase currently known as the last
* phase, or, in other words the start time of the newly introduced phase.
* All calls to addPhase() MUST be with strictly increasing time arguments.
* It is not allowed to add a phase transition that lies in the past relative
* to the current block time.
*/
function addPhase(uint time) internal {
// Throw if new transition time is not strictly increasing
if (N > 0 && time <= phaseEndTime[N-1]) { throw; }
// Throw if new transition time is not in the future
if (time <= now) { throw; }
// Append new transition time to array
phaseEndTime.push(time);
N++;
}
/**
* 2. Define a limit on the amount of time by which the given transition (i)
* can be delayed.
*
* By default, transitions can not be delayed (limit = 0).
*/
function setMaxDelay(uint i, uint timeDelta) internal {
// Throw if index is out-of-range
if (i >= N) { throw; }
maxDelay[i] = timeDelta;
}
/**
* 3. Delay the end of the given phase (n) by the given time delta.
*
* The given phase must not have ended.
*
* This function can be called multiple times for the same phase.
* The defined maximum delay will be enforced across multiple calls.
*/
function delayPhaseEndBy(uint n, uint timeDelta) internal {
// Throw if index is out of range
if (n >= N) { throw; }
// Throw if phase has already ended
if (now >= phaseEndTime[n]) { throw; }
// Throw if the requested delay is higher than the defined maximum for the
// transition
if (timeDelta > maxDelay[n]) { throw; }
// Subtract from the current max delay, so maxDelay is honored across
// multiple calls
maxDelay[n] -= timeDelta;
// Push out all subsequent transitions by the same amount
for (uint i = n; i < N; i++) {
phaseEndTime[i] += timeDelta;
}
}
/**
* 4. End the current phase early.
*
* The current phase must not be the last phase, as the last phase has no end.
* The current phase will end at time now plus the given time delta.
*
* The minimal allowed time delta is 1. This is avoid a race condition for
* other transactions that are processed in the same block.
* Setting phaseEndTime[n] to now would push all later transactions from the
* same block into the next phase.
* If the specified timeDelta is 0 the function gracefully bumps it up to 1.
*/
function endCurrentPhaseIn(uint timeDelta) internal {
// Get the current phase number
uint n = getPhaseAtTime(now);
// Throw if we are in the last phase
if (n >= N) { throw; }
// Set timeDelta to the minimal allowed value
if (timeDelta == 0) {
timeDelta = 1;
}
// The new phase end should be earlier than the currently defined phase
// end, otherwise we don't change it.
if (now + timeDelta < phaseEndTime[n]) {
phaseEndTime[n] = now + timeDelta;
}
}
}
/*
The MIT License (MIT)
Copyright (c) 2016 DFINITY Stiftung
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
* @title: A configurable step function
* @author: Timo Hanke <timo.t.hanke@gmail.com>
*
* The contract implements a step function going down from an initialValue to 0
* in a number of steps (nSteps).
* The steps are distributed equally over a given time (phaseLength).
* Having n steps means that the time phaseLength is divided into n+1
* sub-intervalls of equal length during each of which the function value is
* constant.
*/
pragma solidity ^0.4.6;
contract StepFunction {
uint public phaseLength;
uint public nSteps;
uint public step;
function StepFunction(uint _phaseLength, uint _initialValue, uint _nSteps) {
// Throw if phaseLength does not leave enough room for number of steps
if (_nSteps > _phaseLength) { throw; }
// The reduction in value per step
step = _initialValue / _nSteps;
// Throw if _initialValue was not divisible by _nSteps
if ( step * _nSteps != _initialValue) { throw; }
phaseLength = _phaseLength;
nSteps = _nSteps;
}
/*
* Note the following edge cases.
* initialValue = 0: is valid and will create the constant zero function
* nSteps = 0: is valid and will create the constant zero function (only 1
* sub-interval)
* phaseLength < nSteps: is valid, but unlikely to be intended (so the
* constructor throws)
*/
/**
* Evaluate the step function at a given time
*
* elapsedTime MUST be in the intervall [0,phaseLength)
* The return value is between initialValue and 0, never negative.
*/
function getStepFunction(uint elapsedTime) constant returns (uint) {
// Throw is elapsedTime is out-of-range
if (elapsedTime >= phaseLength) { throw; }
// The function value will bel calculated from the end value backwards.
// Hence we need the time left, which will lie in the intervall
// [0,phaseLength)
uint timeLeft = phaseLength - elapsedTime - 1;
// Calculate the number of steps away from reaching end value
// When verifying the forumla below it may help to note:
// at elapsedTime = 0 stepsLeft evaluates to nSteps,
// at elapsedTime = -1 stepsLeft would evaluate to nSteps + 1.
uint stepsLeft = ((nSteps + 1) * timeLeft) / phaseLength;
// Apply the step function
return stepsLeft * step;
}
}
/*
The MIT License (MIT)
Copyright (c) 2016 DFINITY Stiftung
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
* @title: Contract implementing counters with configurable targets
* @author: Timo Hanke <timo.t.hanke@gmail.com>
*
* There is an arbitrary number of counters. Each counter is identified by its
* counter id, a uint. Counters can never decrease.
*
* The contract has no constructor. The target values are set and re-set via
* setTarget().
*/
pragma solidity ^0.4.6;
contract Targets {
// Mapping from counter id to counter value
mapping(uint => uint) public counter;
// Mapping from counter id to target value
mapping(uint => uint) public target;
// A public getter that returns whether the target was reached
function targetReached(uint id) constant returns (bool) {
return (counter[id] >= target[id]);
}
/*
* Modifying counter or target are internal functions.
*/
// (Re-)set the target
function setTarget(uint id, uint _target) internal {
target[id] = _target;
}
// Add to the counter
// The function returns whether this current addition makes the counter reach
// or cross its target value
function addTowardsTarget(uint id, uint amount)
internal
returns (bool firstReached)
{
firstReached = (counter[id] < target[id]) &&
(counter[id] + amount >= target[id]);
counter[id] += amount;
}
}
/*
The MIT License (MIT)
Copyright (c) 2016 DFINITY Stiftung
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* @title: Configuration parameters for the FDC
* @author: Timo Hanke <timo.t.hanke@gmail.com>
*/
pragma solidity ^0.4.6;
contract Parameters {
/*
* Time Constants
*
* Phases are, in this order:
* earlyContribution (defined by end time)
* pause
* donation round0 (defined by start and end time)
* pause
* donation round1 (defined by start and end time)
* pause
* finalization (defined by start time, ends manually)
* done
*/
// The start of round 0 is set to 2017-01-24 19:00 of timezone Europe/Zurich
uint public constant round0StartTime = 1484676000 + 1 weeks;
// The start of round 1 is set to 2017-05-17 19:00 of timezone Europe/Zurich
// TZ="Europe/Zurich" date -d "2017-05-17 19:00" "+%s"
uint public constant round1StartTime = 1495040400;
// Transition times that are defined by duration
uint public constant round0EndTime = round0StartTime + 6 weeks;
uint public constant round1EndTime = round1StartTime + 6 weeks;
uint public constant finalizeStartTime = round1EndTime + 1 weeks;
// The finalization phase has a dummy end time because it is ended manually
uint public constant finalizeEndTime = finalizeStartTime + 1000 years;
// The maximum time by which donation round 1 can be delayed from the start
// time defined above
uint public constant maxRoundDelay = 270 days;
// The time for which donation rounds remain open after they reach their
// respective targets
uint public constant gracePeriodAfterRound0Target = 1 days;
uint public constant gracePeriodAfterRound1Target = 0 days;
/*
* Token issuance
*
* The following configuration parameters completely govern all aspects of the
* token issuance.
*/
// Tokens assigned for the equivalent of 1 CHF in donations
uint public constant tokensPerCHF = 10;
// Minimal donation amount for a single on-chain donation
uint public constant minDonation = 1 ether;
// Bonus in percent added to donations throughout donation round 0
uint public constant round0Bonus = 200;
// Bonus in percent added to donations at beginning of donation round 1
uint public constant round1InitialBonus = 25;
// Number of down-steps for the bonus during donation round 1
uint public constant round1BonusSteps = 5;
// The CHF targets for each of the donation rounds, measured in cents of CHF
uint public constant millionInCents = 10**6 * 100;
uint public constant round0Target = 1 * millionInCents;
uint public constant round1Target = 20 * millionInCents;
// Share of tokens eventually assigned to DFINITY Stiftung and early
// contributors in % of all tokens eventually in existence
uint public constant earlyContribShare = 22;
}
contract FDC is TokenTracker, Phased, StepFunction, Targets, Parameters {
// An identifying string, set by the constructor
string public name;
/*
* Phases
*
* The FDC over its lifetime runs through a number of phases. These phases are
* tracked by the base contract Phased.
*
* The FDC maps the chronologically defined phase numbers to semantically
* defined states.
*/
// The FDC states
enum state {
pause, // Pause without any activity
earlyContrib, // Registration of DFINITY Stiftung/early contributions
round0, // Donation round 0
round1, // Donation round 1
offChainReg, // Grace period for registration of off-chain donations
finalization, // Adjustment of DFINITY Stiftung/early contribution tokens
// down to their share
done // Read-only phase
}
// Mapping from phase number (from the base contract Phased) to FDC state
mapping(uint => state) stateOfPhase;
/*
* Tokens
*
* The FDC uses base contract TokenTracker to:
* - track token assignments for
* - donors (unrestricted tokens)
* - DFINITY Stiftung/early contributors (restricted tokens)
* - convert DFINITY Stiftung/early contributor tokens down to their share
*
* The FDC uses the base contract Targets to:
* - track the targets measured in CHF for each donation round
*
* The FDC itself:
* - tracks the memos of off-chain donations (and prevents duplicates)
* - tracks donor and early contributor addresses in two lists
*/
// Mapping to store memos that have been used
mapping(bytes32 => bool) memoUsed;
// List of registered addresses (each address will appear in one)
address[] public donorList;
address[] public earlyContribList;
/*
* Exchange rate and ether handling
*
* The FDC keeps track of:
* - the exchange rate between ether and Swiss francs
* - the total and per address ether donations
*/
// Exchange rate between ether and Swiss francs
uint public weiPerCHF;
// Total number of Wei donated on-chain so far
uint public totalWeiDonated;
// Mapping from address to total number of Wei donated for the address
mapping(address => uint) public weiDonated;
/*
* Access control
*
* The following three addresses have access to restricted functions of the
* FDC and to the donated funds.
*/
// Wallet address to which on-chain donations are being forwarded
address public foundationWallet;
// Address that is allowed to register DFINITY Stiftung/early contributions
// and off-chain donations and to delay donation round 1
address public registrarAuth;
// Address that is allowed to update the exchange rate
address public exchangeRateAuth;
// Address that is allowed to update the other authenticated addresses
address public masterAuth;
/*
* Global variables
*/
// The phase numbers of the donation phases (set by the constructor,
// thereafter constant)
uint phaseOfRound0;
uint phaseOfRound1;
/*
* Events
*
* - DonationReceipt: logs an on-chain or off-chain donation
* - EarlyContribReceipt: logs the registration of early contribution
* - BurnReceipt: logs the burning of token during finalization
*/
event DonationReceipt (address indexed addr, // DFN address of donor
string indexed currency, // donation currency
uint indexed bonusMultiplierApplied, // depends stage
uint timestamp, // time occurred
uint tokenAmount, // DFN to b recommended
bytes32 memo); // unique note e.g TxID
event EarlyContribReceipt (address indexed addr, // DFN address of donor
uint tokenAmount, // *restricted* tokens
bytes32 memo); // arbitrary note
event BurnReceipt (address indexed addr, // DFN address adjusted
uint tokenAmountBurned); // DFN deleted by adj.
/**
* Constructor
*
* The constructor defines
* - the privileged addresses for access control
* - the phases in base contract Phased
* - the mapping between phase numbers and states
* - the targets in base contract Targets
* - the share for early contributors in base contract TokenTracker
* - the step function for the bonus calculation in donation round 1
*
* All configuration parameters are taken from base contract Parameters.
*/
function FDC(address _masterAuth, string _name)
TokenTracker(earlyContribShare)
StepFunction(round1EndTime-round1StartTime, round1InitialBonus,
round1BonusSteps)
{
/*
* Set identifying string
*/
name = _name;
/*
* Set privileged addresses for access control
*/
foundationWallet = _masterAuth;
masterAuth = _masterAuth;
exchangeRateAuth = _masterAuth;
registrarAuth = _masterAuth;
/*
* Initialize base contract Phased
*
* |------------------------- Phase number (0-7)
* | |-------------------- State name
* | | |---- Transition number (0-6)
* V V V
*/
stateOfPhase[0] = state.earlyContrib;
addPhase(round0StartTime); // 0
stateOfPhase[1] = state.round0;
addPhase(round0EndTime); // 1
stateOfPhase[2] = state.offChainReg;
addPhase(round1StartTime); // 2
stateOfPhase[3] = state.round1;
addPhase(round1EndTime); // 3
stateOfPhase[4] = state.offChainReg;
addPhase(finalizeStartTime); // 4
stateOfPhase[5] = state.finalization;
addPhase(finalizeEndTime); // 5
stateOfPhase[6] = state.done;
// Let the other functions know what phase numbers the donation rounds were
// assigned to
phaseOfRound0 = 1;
phaseOfRound1 = 3;
// Maximum delay for start of donation rounds
setMaxDelay(phaseOfRound0 - 1, maxRoundDelay);
setMaxDelay(phaseOfRound1 - 1, maxRoundDelay);
/*
* Initialize base contract Targets
*/
setTarget(phaseOfRound0, round0Target);
setTarget(phaseOfRound1, round1Target);
}
/*
* PUBLIC functions
*
* Un-authenticated:
* - getState
* - getMultiplierAtTime
* - donateAsWithChecksum
* - finalize
* - empty
* - getStatus
*
* Authenticated:
* - registerEarlyContrib
* - registerOffChainDonation
* - setExchangeRate
* - delayRound1
* - setFoundationWallet
* - setRegistrarAuth
* - setExchangeRateAuth
* - setAdminAuth
*/
/**
* Get current state at the current block time
*/
function getState() constant returns (state) {
return stateOfPhase[getPhaseAtTime(now)];
}
/**
* Return the bonus multiplier at a given time
*
* The given time must
* - lie in one of the donation rounds,
* - not lie in the future.
* Otherwise there is no valid multiplier.
*/
function getMultiplierAtTime(uint time) constant returns (uint) {
// Get phase number (will throw if time lies in the future)
uint n = getPhaseAtTime(time);
// If time lies in donation round 0 we return the constant multiplier
if (stateOfPhase[n] == state.round0) {
return 100 + round0Bonus;
}
// If time lies in donation round 1 we return the step function
if (stateOfPhase[n] == state.round1) {
return 100 + getStepFunction(time - getPhaseStartTime(n));
}
// Throw outside of donation rounds
throw;
}
/**
* Send donation in the name a the given address with checksum
*
* The second argument is a checksum which must equal the first 4 bytes of the
* SHA-256 digest of the byte representation of the address.
*/
function donateAsWithChecksum(address addr, bytes4 checksum)
payable
returns (bool)
{
// Calculate SHA-256 digest of the address
bytes32 hash = sha256(addr);
// Throw is the checksum does not match the first 4 bytes
if (bytes4(hash) != checksum) { throw ; }
// Call un-checksummed donate function
return donateAs(addr);
}
/**
* Finalize the balance for the given address
*
* This function triggers the conversion (and burn) of the restricted tokens
* that are assigned to the given address.
*
* This function is only available during the finalization phase. It manages
* the calls to closeAssignments() and unrestrict() of TokenTracker.
*/
function finalize(address addr) {
// Throw if we are not in the finalization phase
if (getState() != state.finalization) { throw; }
// Close down further assignments in TokenTracker
closeAssignmentsIfOpen();
// Burn tokens
uint tokensBurned = unrestrict(addr);
// Issue burn receipt
BurnReceipt(addr, tokensBurned);
// If no restricted tokens left
if (isUnrestricted()) {
// then end the finalization phase immediately
endCurrentPhaseIn(0);
}
}
/**
* Send any remaining balance to the foundation wallet
*/
function empty() returns (bool) {
return foundationWallet.call.value(this.balance)();
}
/**
* Get status information from the FDC
*
* This function returns a mix of
* - global status of the FDC
* - global status of the FDC specific for one of the two donation rounds
* - status related to a specific token address (DFINITY address)
* - status (balance) of an external Ethereum account
*
* Arguments are:
* - donationRound: donation round to query (0 or 1)
* - dfnAddr: token address to query
* - fwdAddr: external Ethereum address to query
*/
function getStatus(uint donationRound, address dfnAddr, address fwdAddr)
public constant
returns (
state currentState, // current state (an enum)
uint fxRate, // exchange rate of CHF -> ETH (Wei/CHF)
uint currentMultiplier, // current bonus multiplier (0 if invalid)
uint donationCount, // total individual donations made (a count)
uint totalTokenAmount, // total DFN planned allocated to donors
uint startTime, // expected start time of specified donation round
uint endTime, // expected end time of specified donation round
bool isTargetReached, // whether round target has been reached
uint chfCentsDonated, // total value donated in specified round as CHF
uint tokenAmount, // total DFN planned allocted to donor (user)
uint fwdBalance, // total ETH (in Wei) waiting in fowarding address
uint donated) // total ETH (in Wei) donated by DFN address
{
// The global status
currentState = getState();
if (currentState == state.round0 || currentState == state.round1) {
currentMultiplier = getMultiplierAtTime(now);
}
fxRate = weiPerCHF;
donationCount = totalUnrestrictedAssignments;
totalTokenAmount = totalUnrestrictedTokens;
// The round specific status
if (donationRound == 0) {
startTime = getPhaseStartTime(phaseOfRound0);
endTime = getPhaseStartTime(phaseOfRound0 + 1);
isTargetReached = targetReached(phaseOfRound0);
chfCentsDonated = counter[phaseOfRound0];
} else {
startTime = getPhaseStartTime(phaseOfRound1);
endTime = getPhaseStartTime(phaseOfRound1 + 1);
isTargetReached = targetReached(phaseOfRound1);
chfCentsDonated = counter[phaseOfRound1];
}
// The status specific to the DFN address
tokenAmount = tokens[dfnAddr];
donated = weiDonated[dfnAddr];
// The status specific to the Ethereum address
fwdBalance = fwdAddr.balance;
}
/**
* Set the exchange rate between ether and Swiss francs in Wei per CHF
*
* Must be called from exchangeRateAuth.
*/
function setWeiPerCHF(uint weis) {
// Require permission
if (msg.sender != exchangeRateAuth) { throw; }
// Set the global state variable for exchange rate
weiPerCHF = weis;
}
/**
* Register early contribution in the name of the given address
*
* Must be called from registrarAuth.
*
* Arguments are:
* - addr: address to the tokens are assigned
* - tokenAmount: number of restricted tokens to assign
* - memo: optional dynamic bytes of data to appear in the receipt
*/
function registerEarlyContrib(address addr, uint tokenAmount, bytes32 memo) {
// Require permission
if (msg.sender != registrarAuth) { throw; }
// Reject registrations outside the early contribution phase
if (getState() != state.earlyContrib) { throw; }
// Add address to list if new
if (!isRegistered(addr, true)) {
earlyContribList.push(addr);
}
// Assign restricted tokens in TokenTracker
assign(addr, tokenAmount, true);
// Issue early contribution receipt
EarlyContribReceipt(addr, tokenAmount, memo);
}
/**
* Register off-chain donation in the name of the given address
*
* Must be called from registrarAuth.
*
* Arguments are:
* - addr: address to the tokens are assigned
* - timestamp: time when the donation came in (determines round and bonus)
* - chfCents: value of the donation in cents of Swiss francs
* - currency: the original currency of the donation (three letter string)
* - memo: optional bytes of data to appear in the receipt
*
* The timestamp must not be in the future. This is because the timestamp
* defines the donation round and the multiplier and future phase times are
* still subject to change.
*
* If called during a donation round then the timestamp must lie in the same
* phase and if called during the extended period for off-chain donations then
* the timestamp must lie in the immediately preceding donation round.
*/
function registerOffChainDonation(address addr, uint timestamp, uint chfCents,
string currency, bytes32 memo)
{
// Require permission
if (msg.sender != registrarAuth) { throw; }
// The current phase number and state corresponding state
uint currentPhase = getPhaseAtTime(now);
state currentState = stateOfPhase[currentPhase];
// Reject registrations outside the two donation rounds (incl. their
// extended registration periods for off-chain donations)
if (currentState != state.round0 && currentState != state.round1 &&
currentState != state.offChainReg) {
throw;
}
// Throw if timestamp is in the future
if (timestamp > now) { throw; }
// Phase number and corresponding state of the timestamp
uint timestampPhase = getPhaseAtTime(timestamp);
state timestampState = stateOfPhase[timestampPhase];
// Throw if called during a donation round and the timestamp does not match
// that phase.
if ((currentState == state.round0 || currentState == state.round1) &&
(timestampState != currentState)) {
throw;
}
// Throw if called during the extended period for off-chain donations and
// the timestamp does not lie in the immediately preceding donation phase.
if (currentState == state.offChainReg && timestampPhase != currentPhase-1) {
throw;
}
// Throw if the memo is duplicated
if (memoUsed[memo]) {
throw;
}
// Set the memo item to true
memoUsed[memo] = true;
// Do the book-keeping
bookDonation(addr, timestamp, chfCents, currency, memo);
}
/**
* Delay a donation round
*
* Must be called from the address registrarAuth.
*
* This function delays the start of donation round 1 by the given time delta
* unless the time delta is bigger than the configured maximum delay.
*/
function delayDonPhase(uint donPhase, uint timedelta) {
// Require permission
if (msg.sender != registrarAuth) { throw; }
// Pass the call on to base contract Phased
// Delaying the start of a donation round is the same as delaying the end
// of the preceding phase
if (donPhase == 0) {
delayPhaseEndBy(phaseOfRound0 - 1, timedelta);
} else if (donPhase == 1) {
delayPhaseEndBy(phaseOfRound1 - 1, timedelta);
}
}
/**
* Set the forwarding address for donated ether
*
* Must be called from the address masterAuth before donation round 0 starts.
*/
function setFoundationWallet(address newAddr) {
// Require permission
if (msg.sender != masterAuth) { throw; }
// Require phase before round 0
if (getPhaseAtTime(now) >= phaseOfRound0) { throw; }
foundationWallet = newAddr;
}
/**
* Set new authenticated address for setting exchange rate
*
* Must be called from the address masterAuth.
*/
function setExchangeRateAuth(address newAuth) {
// Require permission
if (msg.sender != masterAuth) { throw; }
exchangeRateAuth = newAuth;
}
/**
* Set new authenticated address for registrations
*
* Must be called from the address masterAuth.
*/
function setRegistrarAuth(address newAuth) {
// Require permission
if (msg.sender != masterAuth) { throw; }
registrarAuth = newAuth;
}
/**
* Set new authenticated address for admin
*
* Must be called from the address masterAuth.
*/
function setMasterAuth(address newAuth) {
// Require permission
if (msg.sender != masterAuth) { throw; }
masterAuth = newAuth;
}
/*
* PRIVATE functions
*
* - donateAs
* - bookDonation
*/
/**
* Process on-chain donation in the name of the given address
*
* This function is private because it shall only be called through its
* wrapper donateAsWithChecksum.
*/
function donateAs(address addr) private returns (bool) {
// The current state
state st = getState();
// Throw if current state is not a donation round
if (st != state.round0 && st != state.round1) { throw; }
// Throw if donation amount is below minimum
if (msg.value < minDonation) { throw; }
// Throw if the exchange rate is not yet defined
if (weiPerCHF == 0) { throw; }
// Update counters for ether donations
totalWeiDonated += msg.value;
weiDonated[addr] += msg.value;
// Convert ether to Swiss francs
uint chfCents = (msg.value * 100) / weiPerCHF;
// Do the book-keeping
bookDonation(addr, now, chfCents, "ETH", "");
// Forward balance to the foundation wallet
return foundationWallet.call.value(this.balance)();
}
/**
* Put an accepted donation in the books.
*
* This function
* - cannot throw as all checks have been done before,
* - is agnostic to the source of the donation (on-chain or off-chain)
* - is agnostic to the currency
* (the currency argument is simply passed through to the DonationReceipt)
*
*/
function bookDonation(address addr, uint timestamp, uint chfCents,
string currency, bytes32 memo) private
{
// The current phase
uint phase = getPhaseAtTime(timestamp);
// Add amount to the counter of the current phase
bool targetReached = addTowardsTarget(phase, chfCents);
// If the target was crossed then start the grace period
if (targetReached && phase == getPhaseAtTime(now)) {
if (phase == phaseOfRound0) {
endCurrentPhaseIn(gracePeriodAfterRound0Target);
} else if (phase == phaseOfRound1) {
endCurrentPhaseIn(gracePeriodAfterRound1Target);
}
}
// Bonus multiplier that was valid at the given time
uint bonusMultiplier = getMultiplierAtTime(timestamp);
// Apply bonus to amount in Swiss francs
chfCents = (chfCents * bonusMultiplier) / 100;
// Convert Swiss francs to amount of tokens
uint tokenAmount = (chfCents * tokensPerCHF) / 100;
// Add address to list if new
if (!isRegistered(addr, false)) {
donorList.push(addr);
}
// Assign unrestricted tokens in TokenTracker
assign(addr,tokenAmount,false);
// Issue donation receipt
DonationReceipt(addr, currency, bonusMultiplier, timestamp, tokenAmount,
memo);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment