NOTE: This repo and the sale contained within are currently under security review and are subject to change. This note will be lifted when the sale contract (Sale.sol) is finalized.
This repo contains the contracts that will be deployed (Sale.sol
and GRID.sol
). The latter is a modified ERC20 token, which includes a function called provable_redemption
that conforms to the EIP661 specification, which is a specific instance of the more abstract EIP662.
Three token sale simulations are included, which are run using sol-coverage
. Sale.sol
reaches 100% coverage and every line of provable_redemption
is covered. The rest of GRID.sol
is the normal ERC20 specification.
This repo contains a series of simulations of the Grid+ token sale, which will be a discontinuous reverse Dutch auction.
To get set up, run the following:
npm install
And in a new terminal window, run:
npm run coverage
The following is a walkthrough of the Grid+ token sale process.
The Grid+ token sale will run in a discontinuous Reverse Dutch Auction. A fixed number of GRID tokens will be created and some subset of those will be transferred to the Sale.sol
contract. The token sale is then parameterized by calling two separate functions, of which each may only be called once. After the first function is called (which parameterizes the dynamic reward function), pre-sale participants may start sending ether to the contract. Note that pre-salers are under contractual obligation to provide a pre-determined amount of ether and may be kicked out of the sale and blacklisted before their ether is returned. Once the second parameterization function is called (which sets the ether cap and Rmax
) and the starting block is reached, the crowd may send ether until the ending block is reached. Note that pre-sale participants may send ether at any time before the starting block is reached. Once the cap is reached, the final reward (a function of the blocks elapsed since the starting block) is calculated and applied to all participants. Note that this reward (in units of GRID/ETH) has a ceiling at Rmax
. The number of GRID tokens rewarded to each participant is determined both by the final reward and the amount of ether contributed and those GRID tokens may be sent to the participant by any Ethereum user once the sale is over. Note that pre-sale participants receive up to a 15% higher reward value (which still has a ceiling at Rmax).
The Grid+ token sale will occur in a discontinuous Reverse Dutch Auction. This means that for the first part of the sale, the reward (GRID/ETH) is a function of the blocks elapsed since the beginning of the sale. At some point, the reward reaches Rmax
, a pre-defined parameter. For the remainder of the sale, the reward is fixed at Rmax
. An example curve is shown below, where Rmax
is roughly ~480 GRID/ETH.
The following is the series of steps that will occur during the token sale.
Sale.sol
bytecode is deployed to Ethereum. The deploying address is set as the admin
. The admin
may be changed at any time by the current admin
by calling SwitchAdmin()
with a new address as the parameter.
The admin may parameterize the sale to fit the following curve:
R = (Rmax/a_1) + (Rmax * (bi-b0))/a_2
This curve describes R
, the reward (in units of GRID/ETH) as a function of the blocks elapsed since the starting block, with bi
being the current block and b0
being the starting block. a_1
and a_2
are pre-determined parameters and Rmax
is the reward value at which 100% of tokens are sold in this sale.
It is important to note that R
cannot exceed Rmax
.
SetupSale()
may be called by the admin (by default, the address that deployed the contract). It takes the following parameters:
uint _start The block number on which the sale begins
uint length The number of blocks for which the sale will run. _start + length = end block
uint _a_1 The a_1 parameter in the function above
uint _a_2 The a_2 parameter in the function above
This function may only be called once by the admin and will throw
otherwise. Note that Rmax
is set when the cap is determined. This may happen shortly after the initial parameterization.
Pre-sale participants may be whitelisted at any time by the admin
using WhitelistPresale()
. This only takes one parameter:
address user The address to allow into the Pre-sale
Pre-sale participants must deposit either 0 ether or the exact amount of ether they have pledged, per their individual pre-sale agreement. If this restriction is violated, Grid+ is contractually allowed to remove the pre-sale participant from the pre-sale (however, this participant is still allowed in the regular sale). The removal is done by calling VentPresale()
by the admin
, which also takes one parameter:
address user The address to blacklist from the pre-sale. Any ether contributed is returned.
Whitelisted pre-sale participants may contribute to the pre-sale at any time before the starting block (start
) has been reached. This is done by sending ether to the contract address (covered in the default function function()
). Once start
has elapsed, the pre-sale is over and presale
addresses may no longer contribute.
The cap may be added any time before the starting block. It may only be set once and must be set by the admin. SetCap()
takes 2 parameters:
uint _cap The sale cap in wei
uint _Rmax The maximum reward value in GRID/ETH
This function must be called by the admin
before the starting block.
Once start
has been reached, the sale officially begins. Any Ethereum address (excluding pre-sale participants) may send ether to the sale via the default function()
. This ether will be accepted unless one of the following conditions is met:
1. block.number > end block
2. The msg.value, when added to the contract balance, exceeds the cap
3. Address is in the pre-sale
4. The cap has not been set
If none of these conditions are met, the contributor will trigger the following actions:
1. Record the amount of wei sent by the contributing address
2. Add msg.value to the total wei contributed
3. Calculate the reward of this block and update Rf (the final reward)
4. Emit an event capturing the contribution
Once the sale is over (either because the cap has been met or because the end block has been reached), participants may withdraw tokens themselves or Grid+ may trigger the withdrawal. The Withdraw()
function may be called by any Ethereum address and takes one parameter:
address user User to whom the GRID tokens should be withdrawn
This function may be called at any time after the sale. The amount contributed by the user in question is copied to memory and the mapping is zeroed out. With this contribution amount (in wei), the user reward (in GRID) is calculated using the GetUserReward()
function. These GRID tokens are then transferred to the user and an event is emitted.
Note that GetUserReward()
includes a 1.15x multiplier for pre-sale participants, so long as this does not exceed Rmax
. If the result of this value (1.15*Rf
) is greater than Rmax
, it is simply replaced by the value of Rmax
.
Depending on the value of Rf
, the sale contract may retain some number of GRID tokens, which can be withdrawn once all participants have had their GRID tokens withdrawn. This is triggered with MoveGRID()
, which may only be called by the admin
. This may only be called once the block number is greater than the end block of the sale. It takes 1 parameter:
address to The address to whom the excess GRID will be transferred
Ether may be withdrawn from the contract at any time by the admin
using MoveFunds()
. Similar to MoveGRID()
, the function takes an address to move the ether to.
At this point the sale is concluded!
In the event that something goes tragically wrong with the Grid+ token sale, an escape hatch has been provided. Once it is open, the sale immediately stops and all participants may withdraw any ether they contributed.
Only the admin
may open the hatch (which cannot be closed once open) and does so by calling Escape()
, which takes no parameters.
Once the hatch is open, any Ethereum address may call Abort()
, which takes one parameter:
address user Participant to be refunded
The amount contributed by the user in question is copied to memory and the mapping is zeroed out. This amount is then refunded to the user.