@vidy-dev/ethereum-ico

Contracts related to the ICO sales

Usage no npm install needed!

<script type="module">
  import vidyDevEthereumIco from 'https://cdn.skypack.dev/@vidy-dev/ethereum-ico';
</script>

README

ethereum-ico

Contracts for the VidyCoin ICO: vault, sales, whitelists

Install

$ yarn add @vidy-dev/ethereum-ico

or

$ npm install @vidy-dev/ethereum-ico

ICOVault

Some fundraising will be conducted outside the Ethereum blockchain, and so some of the 5.2 billion VidyCoins allocated for the ICO will be distributed according to terms enforced by paper contracts rather than the blockchain itself. The whitelisted pre-ICO and public ICO sales rounds will occur within the blockchain, however, and the ICOVault contract will hold all ether raised by those rounds in escrow until the ICO period is over or the soft cap is reached. If the ICO is unsuccessful, the ether is made available to the investors who initially deposited it. If the soft cap is reached, the ether is transferred to the InvestorWallet, in a lump sum at the end of the sale or, optionally, in increments as the sale continues.

The ICOVault serves multiple purposes:

  1. Holding ether in escrow, only allowing us to retrieve it if the fundraising soft cap was reached. This establishes a trustless guarantee that refunds will be provided if the ICO fails.
  2. Providing one single canonical reference point to determine if the ICO soft cap has been reached (the ICOVault has a balance of deposited ether, and a publicly readable goal balance).
  3. Enforcing start / stop dates on the ICO in a trustless way (the expiration date of the ICOVault cannot be altered once the VidyCoin sale has begun).
  4. Recording the total amount of ether contributed by any given investor across the trustless sales rounds, preventing refunds exceeding this value.

ICOVault Contract Code

ICOVault.sol is the contract representing the fundraising vault. Through multiple inheritance it provides an implementation of the abstract contract in Vault.sol. The Vault interface is heavily influenced by the example crowdsale vault provided by openzeppelin-solidity: https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/distribution/utils/RefundVault.sol; changes were made in the behavior of ICOVault compared to this example in order to support specific requirements for our crowdsale. The main differences between our sale and the OpenZeppelin reference:

  1. Multiple sale contracts are authorized to make deposits into the vault (represented as a whitelist held by the vault).
  2. Only those sales contracts may initiate refunds (so that their own records can be updated at the same time).
  3. The ICOVault holds all the necessary information to determine the status of the VidyCoin ICO: how much has been raised on the blockchain thus far, the soft cap (goal) of the raise, on what date the ICO will finish, etc. That information is also publicly queriable.
  4. Under the appropriate circumstances, the vault's state can be changed to Refunding or Closed by anyone. We intend to execute this transaction ourselves, but allowing anyone to do so establishes trustlessly that refunds will be provided in the event of an unsuccessful ICO.

The ICOVault descends from Ownable, NoRemovalWhitelist, and Schedulable, three utility contracts. Ownable is part of the OpenZeppelin suite and establishes one single Ethereum address as the "owner" of the contract, authorized to perform some specific set of functions. NoRemovalWhitelist allows certain functions to be limited to a list of "whitelisted" addresses, a list which can be grown by the owner adding new addresses but not shrunk. Schedulable gives the contract activation and expiration times, with specific behavior based on its current state. Although a Schedulable contract can freely be rescheduled by its owner as long as it has not yet activated, once the activation time arrives, that time and the expiration time become immutable.

The main function of the vault is to store ether deposited by investors for the sale period, and once that sale period ends either forwarding it to a wallet owned by us or allowing investors to receive refunds (depending on whether the ICO reached its goal). However, we don't intend for investors to interact with the vault directly; they interact with specific sale contracts (each with different sale terms), with those sale contracts making deposits to the vault on behalf of investors, and initiated refunds from the vault when appropriate. To ensure that sales records are consistent with vault deposit records, only the sale contracts are allowed to invoke the deposit and refund vault methods, although the result of those methods (deposit records and events) are visible to anyone.

The vault, being the single deposit point for all token sales, is the contract which determines whether our ICO fundraising goal (soft cap) has been reached. If so, the ether raised will be transferred to the InvestorWallet address; if not, refunds are made available to ICO customers. To guarantee this behavior, transition between vault states (Active -> Closed or Active -> Refunding) may be initiated by anyone, but only under specific contract conditions. This provides a trustless guarantee that refunds will be offered (authorizing refunds for an unsuccessful fundraise does not require a member of the Vidy team).

Ownership

The owner of the vault is an address authorized to perform certain configuration function calls; it will most likely be the address responsible for deploying the ICOVault contract. The contract does not allow the owner to withdraw ether or change the intended recipient of the ether collected.

Abilities of the owner:

  1. Adding addresses to the deposit whitelist (token sale contracts will be added to the whitelist as those contracts are deployed).
  2. Rescheduling the vault's activation and expiration time if the ICO is delayed. Only allowed until the current activation time, at which point both values become immutable.
  3. Changing the fundraising goal within the range of the immutable minimum and maximum. Only allowed until the expiration time of the vault.

Whitelisting

Only whitelisted addresses may make deposits into, or initiate refunds out of, the ICOVault. In practice these whitelist addresses will be the addresses of sale contracts, each distributing VidyCoins under specific terms as a raise round of the ICO, and initiating refunds from the vault when requested and appropriate.

Scheduling

The activation and expiration times of the vault corresponding to the start and end times of the portion of the VidyCoin ICO performed on the Ethereum network: the pre-ICO raise and public ICO raise. Although the ICOVault can be rescheduled if necessary up until the beginning of the pre-ICO raise, once fundraising has begun and the vault activates, its expiration time is immutable.

The ICOVault can only be transitioned from an Active state to Closed or Refunding after the expiration time.

Deposit Limits

The ICOVault allows minimum and maximum contributions from particular addresses. Deposits will be refused if smaller than the minimum amount, or if they would raise the total deposited by that account above the maximum.

If set, these limits are applied per-investor-address, meaning it would be possible to evade the contribution maximum by depositing from multiple wallets. This cannot be prevented by the blockchain itself; instead, it is the responsibility of off-blockchain KYC verification to ensure that a particular investor does not register multiple wallet addresses -- that each new account address added to the sale whitelists represents a new individual who has not previously registered an address.

Goal

The ICOVault has a publicly readable goal of ether raised, which the vault owner can alter within a prespecified range to account for fluctuations in ether value. The goal can only be adjusted up to the vault's expiration time, after which point the goal has either been reached, or it hasn't.

The VidyCoin offering will be performed in multiple stages, including fundraising that occurs outside the blockchain before the public ICO is opened. Because of this additional fundraising period, the goal specified in the ICOVault contract will be strictly less than the total ICO goal given in the whitepaper -- the ICOVault goal will be set such that, when summed with the earlier fundraising, the total whitepaper goal is reached.

Deposits and Refunds

Deposits initiated by the whitelisted sale contracts are made in ether and accompanied by a depositor address; the ICOVault records the amount received as belonging to that address. Deposits can only be made while the vault is an Active state.

Refunds initiated by the whitelisted sale contracts cause any amount of ether, up to the total deposited on behalf of the recipient, to be transferred directly from the vault to the depositing address. Refunds can only be initiated when the vault is in the Refunding state.

State Transitions

As a Schedulable contract, the ICOVault is in an "unactivated" state until the activation time, specified as seconds since the epoch, is reached (determined by the value now within a transaction execution block). While unactivated, the activation and expiration times can be rescheduled if the ICO is delayed. Once the ICO has begun, determined by the execution block being at or beyond the activation time, the vault expiration cannot be delayed or altered.

The vault begins in an Active state. The functions close() and allowRefunds(), callable by anyone, cause a permanent state transition to Closed and Refunding respectively. These functions will revert unless the transaction block is executed at or after the vault's expiration time, and the amount of ether raised and held by the vault is appropriate for the requested state: at least as much as the goal value for close(), less than the goal value for allowRefunds().

In other words, if the fundraising goal is not met, no member of the Vidy team is needed for the vault to enter a Refunding state. This supports a trustless guarantee that refunds will be honored. Similarly, if the goal is reached, it is easy to transition it to a Closed state and thus transfer the ether raised to the InvestorWallet (provided as the wallet address for the vault).

Whitelisting

The ICOVault is deployed as a separate step from the migration of the sale contracts. When the sale contracts are deployed and their addresses known, those addresses are added to the vault's whitelist.

Internally, the ICOVault uses NoRemovalWhitelist as a supercontract; the sale contracts added to the whitelist cannot later be removed. This guarantees protection against a possible malicious action by the vault owner: removing a whitelisted sale to prevent it from issuing refunds. This action would not benefit the vault owner as it would not allow them to withdraw the ether themselves (Closed and Refunding states are still mutually exclusive, and state transition is unrelated to whitelisting), but it is prevented nonetheless.

Public interface

Construction

The ICOVault constructor requires a wallet address (to which ether will be transferred in the event of a successful ICO), a goal (soft cap), minimum and maximum goals, and an initial whitelist. When deployed to the Ethereum network, it will receive additional setup from the migration script, eventually settling with this configuration:

  1. The immutable wallet address, to which ether will be sent after a successful ICO. This will be the InvestorWallet contract.
  2. A contract owner (the account deploying contracts to the Ethereum network).
  3. Activation and expiration times, mutable by the contract owner until the activation time is reached, then immutable.
  4. A current ether fundraising goal (specified in wei). The goal can be adjusted by the owner up to the vault's expiration time.
  5. Minimum and maximum values for the fundraising goal. Immutable bounds on any changes made to the vault's goal. We intend to adjust the goal only to respond to unanticipated changes in ether value; these immutable bounds trustlessly limit our ability to do so.
  6. In the Active state, of possible states {Active, Closed, Refunding}.

After this configuration, the Ethereum address which deployed the ICOVault contract retains ownership. This is used during later deployment of the sale contracts so they can be added to the vault's whitelist. The owner address, and the address to which ether is forwarded upon a successful ICO, are not the same; "ownership" in this case allows contract administration but not receipt of funds.

Events

event Closed(): Emitted when the vault transitions to Closed state.

event RefundsEnabled(): Emitted when the vault transitions to Refunding state.

event Withdrawn(uint256 amount): Emitted when ether is withdrawn from the vault into the wallet, including when the vault is Closed but also if withdraw() is called before the end of the sale (if the goal has been reached).

event GoalReached(uint256 amount): Emitted when a transaction causes the total deposited to exceed the goal (either because a deposit was made or the goal was lowered).

event Deposited(address indexed depositor, uint256 amount): Emitted when a deposit was made. depositor is the address of the investor providing the funds, not the address which made the deposit function call. In our design, depositor is the investor specified as an argument to deposit, which is called by a sale contract. amount is in wei.

event Refunded(address indexed depositor, address indexed beneficiary, uint256 amount): Emitted when a refund is made. depositor is the original depositor of the funds; beneficiary is who they were sent to. In our implementation the two will always be the same address. amount is in wei.

event OwnershipRenounced(address indexed previousOwner): Emitted when the current owner renounces ownership and the contract transitions permanently into a non-owned state.

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner): Emitted when the current owner transfers ownership to another address.

event WhitelistedAddressAdded(address indexed addr): Emitted when an address is added to the internal whitelist (i.e. a sale contract is authorized to make deposits).

Fields and Constant Functions

mapping (address => uint256) public deposited: Records the amount of ether, in wei, deposited by any given address. Adjusts when deposits are made or refunds issued.

uint256 public totalDeposited: Records the total amount deposited across all investors. Adjusts when deposits are made or refunds issued.

uint256 public depositMinimum: The minimum amount accepted as a deposit. If an attempt is made to deposit a total amount of ether lower than this value (in wei), the deposit will be refused. This calculation is made with respect to the resulting deposit total for the address, meaning a first-time deposit lower than this value will revert, but if an investor already has a nonzero amount deposited, a small additional deposit may be accepted.

uint256 public depositMaximum: The maximum amount accepted as a deposit. If an attempt is made to deposit a total amount of ether greater than this value (in wei), the deposit will be refused. This calculation is made with respect to the resulting deposit total for the address, meaning an investor who has already deposited a significant amount will only be able to make additional deposits up to a combined total of this value.

uint256 public goal: The current fundraising goal for the vault, in wei. Alterable by the contract owner within the immutable minimumGoal and maximumGoal, and only until expirationTime is reached.

uint256 public minimumGoal: The lower bound for goal adjustment during the sale period (before expirationTime is reached). Immutable.

uint256 public maximumGoal: The upper bound for goal adjustment during the sale period (before expirationTime is reached). Immutable.

address public wallet: The address to which ether will be sent when the vault transitions to the Closed state. Immutable.

State public state: The current state of the vault: {Active, Closed, Refunding}.

address public owner: The address of the contract owner. Some functions will revert if msg.sender is not this address.

uint256 public activationTime: The time, in seconds since the epoch, after which changes to its expirationTime are disallowed.

uint256 public expirationTime: The time, in seconds since the epoch, at which the ICO will end.

mapping (address => bool) public whitelist: Whether a given address appears on the whitelist; i.e. if that address is authorized to initiate deposits or refunds. Some functions will revent if whitelist[msg.sender] is not true.

Non-constant Functions

function transferOwnership(address newOwner) public: Set owner = newOwner. Requirements: msg.sender == owner.

function renounceOwnership() public: Set owner = address(0). Requirements: msg.sender == owner.

function addAddressToWhitelist(address _person) public: Add the provided address to whitelist, allowing that address to perform certain whitelist-only function calls. Requirements: msg.sender == owner.

function addAddressesToWhitelist(address[] _people) public: Add the provided addresses to whitelist, allowing them to perform certain whitelist-only function calls. Requirements: msg.sender == owner.

function removeAddressFromWhitelist(address _person) public: Provided by the supercontract Whitelist interface, but always reverts.

function removeAddressesFromWhitelist(address[] _people) public: Provided by the supercontract Whitelist interface, but always reverts.

function schedule(uint256 _activationTime, uint256 _expirationTime) public returns (bool success): Sets activationTime and expirationTime to the provided values. Requirements: msg.sender == owner, and the execution block occurs before the previous value of activationTime.

function activate() public returns (bool success): Sets activationTime to the execution block timestamp. Requirements: msg.sender == owner, and the execution block occurs before the previous value of activationTime.

function cancel() public returns (bool success): Sets activationTime and expirationTime to 0. Requirements: msg.sender == owner, and the execution block occurs before the previous value of activationTime.

function deposit(address _investor) public payable: Deposit the transferred ether into the vault on behalf of the indicated investor, updating deposited and totalDeposited. The ether received will be included in the lump transfer to the vault's wallet when it transitions to a Closed state, or made available as a refund to _investor when it transitions to Refunding. Requirements: msg.sender is on the whitelist and the vault is in Active state.

function refund(address _investor) public: Transfer all ether deposited by _investor back to their address, updating deposited and totalDeposited accordingly. Requirements: msg.sender is on the whitelist and the vault is in Refunding state.

function refund(address _investor, uint256 _amount) public: Transfer _amount ether deposited by _investor back to their address, updating deposited and totalDeposited accordingly. Requirements: msg.sender is on the whitelist, the vault is in Refunding state, and deposited[_investor] is at least _amount.

function close() public: Transition the vault to Closed state, transferring its entire balance of stored ether to the wallet address which was provided at construction. Requirements: the execution block timestamp at least expirationTime, the vault is in Active state, and totalDeposited is at least the current goal.

function allowRefunds() public: Transition the vault to Refunded state. Refunds are provided upon request through the refund functions. Requirements: the execution block timestamp at least expirationTime, the vault is in Active state, and totalDeposited is less than the current goal.

function changeGoal(uint256 _newGoal) public: Change the goal (soft cap) of the vault to the specified amount, represented in wei. A comparison between totalDeposited and goal determines the allowed state transition after the vault's expiration time. This function allows the Vidy team some flexibility to adjust the ICO soft cap to account for unanticipated fluctuations in ether value. Requirements: msg.sender == owner, the execution block timestamp is less than expirationTime, and the requested goal is within the range bounded by minimumGoal and maximumGoal.

function withdraw() public: Transfer all accumulated ether in the vault to the wallet address which was provided at construction. Requirements: the vault is in Active state, and totalDeposited is at least the current goal.

Implementation Notes

The SafeMath library used for all arithmetic is drawn from the openzeppelin-solidity suite: https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol. This library protects against integer overflow / underflow and in so doing prevents refund overdrafts.

ICOVault is a descendent of Vault, an abstract contract specifying the basic vault interface but leaving implementation to subcontracts. Different aspects of vault functionality: time and deposit-dependent state transitions, whitelisted access to deposit and refund functions, etc., are implemented in small, separate abstract subcontracts; the behavior is combined in a concrete contract using multiple inheritance.

When ICOVault is deployed, a significant amount of funding will likely already have been raised from sources outside the blockchain. To account for this, the vault's goal value will not be the full soft cap specified in the whitepaper; it will have been adjusted downward to represent the remaining amount needed to reach the fundraising goal.

The minimum and maximum deposit limits are applied per-address, so an investor depositing under multiple addresses could potentially exceed the maximum limit. The responsibility to prevent this is held off-blockchain, at the KYC verification stage; the sale contracts only allow contributions from whitelisted addresses, and each individual who passes KYC verification will only be able to register one single address to that whitelist.

Sales

Some early fundraising and VidyCoin distribution will occur outside of the blockchain. The final stages of the ICO, the whitelisted pre-ICO raise and the public ICO raise, are each managed trustlessly on the blockchain as sale contracts. The design and source code of each of the two sales is very similar, differing only in a few key details. Both sale contracts share the ICOVault described above, both to store ether received from sales, and as the determiner of whether the ICO was successful (or refunds should be issued).

Each sale, pre-ICO and public ICO, is structured as a pair of contracts: one acting as the sale contract and one as a whitelist for that sale. The sale contracts use the ICOVault to measure progress towards the ICO soft cap; the hard cap is represented implicitly as the total token inventory of the sale multiplied against the sale price (i.e., the sale contract can only raise as much ether as it has VidyCoins to distribute).

As described in the whitepaper, VidyCoins purchased from the pre-ICO sale are held in lock-up until after the completion of the ICO (the sale contract itself acts as the lock-up in this case). VidyCoins purchased from the public sale are received immediately.

For both sales, in the event of an unsuccessful fundraise, purchasers are required to surrender their purchased VidyCoins to receive their refund. For the pre-ICO this is easily done, since those VidyCoins will still be held by the sale contract in lock-up. The public sale provides refunds using the approve -> transferFrom convention for ERC20 tokens. This refund function is available only to the original purchaser of the VidyCoins, and only up to the quantity purchased through the sale. I.e. the contract provides refunds with proof of sale and return of the tokens; it does not offer a buy back of VidyCoins presented from arbitrary addresses, or refunds to investors who have sold or otherwise distributed their VidyCoins.

Public Sale Contracts

The public ICO sale period is the final stage of the ICO; the expirationTime of the ICOVault corresponds to the end of the public sale period.

Although the public sale is open to anyone, the sale contract will only process purchases that originate from an address on an external whitelist. Customer addresses are added to this whitelist by our website upon successful completion of a third-party KYC flow: in other words, the whitelist represents members of the public whose proof of identity have been verified by KYC, with no other requirements.

The sale contract itself distributes an inventory of VidyCoins that is provided by the VidyTeamWallet (the initial owner of all VidyCoins). The specific number of VidyCoins to be provided to the sale contract is determined by the minimum allocation for the public raise, plus any unsold inventory from the pre-ICO period. At no point are these unsold VidyCoins held by an address directly controlled by a single individual: they are drained from the pre-ICO sale contract by the VidyTeamWallet (the only account able to do this), requiring multisignature verification, and then transferred to the public ICO contract with another multisignature verified transaction.

Because the public sale offering period is only one stage of the blockchain-managed fundraise, the public sale contract has its own activationTime and expirationTime. The public sale and ICOVault share the same expirationTime while the sale's activationTime is after that of the vault.

Public Sale Whitelist

As mentioned, the public sale contract processes sales only to Ethereum addresses which appear on an external whitelist contract. This whitelist contract, PublicICOWhitelist, is managed by our website and backend API and holds the addresses provided by potential investors who have completed third-party KYC verification.

The whitelist is only used to limit access to the sale contract's purchase function. Once VidyCoins are purchased by an address, removal of that address from the whitelist does not affect their ability to return those tokens later.

The whitelist contract itself shares an interface with the openzeppelin-solidity whitelist implementation, although its internal structure is simpler. The contract is deployed separately so that its owner address (the backend API) can differ from that of the sale contract (owned by the VidyTeamWallet).

Public Sale Whitelist Interface

The PublicICOWhitelist contract is empty when deployed, and owned by the website and backend API account. A third party KYC service SDK, integrated with our website, will provide verification that a potential investor has verified their identity. In response, the website will submit a blockchain transaction adding that customer's Ethereum address to the whitelist.

Construction

The whitelist is constructed and configured by the migration script such that it is empty of addresses. Ownership is transferred to the API backend. Addresses will be added as potential investors complete the KYC validation process hosted on our website.

Events

event WhitelistedAddressAdded(address indexed addr): Emitted when an address is added to the whitelist.

event WhitelistedAddressRemoved(address indexed addr): Emitted when an address is removed from the whitelist.

Fields and Constant Functions

mapping (address => bool) public whitelist: Whether the given address appears on the whitelist.

Non-Constant Functions

function addAddressToWhitelist(address _person) public: Add the provided address to the contract whitelist. Requirements: called by the contract owner.

function addAddressesToWhitelist(address[] _people) public: Add the provided addresses to the contract whitelist. Requirements: called by the contract owner.

function removeAddressFromWhitelist(address _person) public: Remove the provided address from the contract whitelist. Requirements: called by the contract owner.

function removeAddressesFromWhitelist(address[] _people) public: Remove the provided addresses from the contract whitelist. Requirements: called by the contract owner.

Public Sale

The public sale contract PublicICOSale holds a balance of VidyCoins, which it provides to investors upon purchase via an ether transfer. The sale supplies tokens according to a fixed exchange rate (price), which is the same for all customers. VidyCoins are transferred immediately upon purchase (within the same transaction) to the address which provided the ether. The total amount of VidyCoins distributable by the sale contract is determined by the contract's VIDY balance, which it receives via transfer from the VidyTeamWallet (the initial holder of all VidyCoins before distribution).

The sale contract keeps a record of the total amount of ether received from each investor address along with the number of VidyCoins purchased; if refunds are provided, a given investor can only receive refunds up to the amount received through the sale contract itself. In other words, if a single investor purchases VidyCoins from both the public sale and pre-ico sale, they would need to request refunds from each to receive their full investment amount.

Purchases from the sale contract are made automatically upon receipt of ether via the contract's fallback method. Returns follow the ERC20 token standard pattern of approve -> transferFrom, meaning the investor needs to first invoke approve(..) on the VidyCoin contract and then return(..) on the sale, for the refund to be processed.

Public Sale Interface

PublicICOSale, like ICOVault, is Ownable and Schedulable. It also references an external whitelist contract, only allowing purchases from addresses that appear on that list. The VidyCoins it distributes are received via transfer from the VidyTeamWallet, which is also authorized to retrieve them (i.e., the VidyTeamWallet can cause the sale contract to transfer VidyCoins back to the wallet).

Construction

The PublicICOSale constructor sets the token sale price, and addresses for the ERC20 token, vault, and whitelist contracts. These values are immutable. Price is expressed as a ratio, a conversion rate from token to ether, when both are expressed in the smallest possible subunit.

After construction, the deployment script continues configuration of the sale. The sale is scheduled, setting its activationTime and expirationTime according to the whitepaper terms (the expirationTime will correspond to that set on the ICOVault). Ownership of the sale is transferred to the VidyTeamWallet, and a transaction submitted to that wallet to transfer VidyCoins to the sale. Finally, the sale contract address is added to the ICOVault whitelist, causing the vault to accept deposits sent by the sale contract (which are made on behalf of investors).

Like all Schedulable contracts, the activationTime and expirationTime of the sale can be changed by the owner up to the moment the current activationTime is reached, as determined by examining the execution block timestamp. Once activationTime has been reached, the expirationTime becomes immutable. In other words, once the public sale period begins, the time at which it ends cannot be changed; similarly, the public ICO sale period is necessarily bounded by activation and expiration times of the ICOVault, since the vault must be in Active state to receive deposits.

Events

event Sale(address indexed to, uint256 price, uint256 count): Emitted when a purchase of at least 1 subunit of VidyCoin is made. price is the ether received, in wei; count is the number of VidyCoins provided, in the smallest possible subunit. i.e. a price of 1e18 is 1 ETH; a count of 1e18 is 1 VIDY.

event Refund(address indexed to, uint256 price, uint256 count): Emitted when at least 1 wei worth of VidyCoin is returned. price is the ether returned to the investor, in wei; count is the number of VidyCoins returned, in the smallest possible subunit. i.e. a price of 1e18 is 1 ETH; a count of 1e18 is 1 VIDY.

event OwnershipRenounced(address indexed previousOwner): Emitted when the current owner renounces ownership and the contract transitions permanently into a non-owned state.

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner): Emitted when the current owner transfers ownership to another address.

Fields and Constant Functions

uint256 public priceNumerator: The numerator of the ratio expressing a VidyCoin -> ether exchange rate. If the ratio is > 1, a VidyCoin is worth more than an ETH of ether.

uint256 public priceDenominator: The denominator of the ratio expressing a VidyCoin -> ether exchange rate. If the ratio is < 1, a VidyCoin is worth less than an ETH of ether.

uint256 public amountRaised: The total amount of ether, in wei, that has been raised by purchases from this sale. Does not reflect the total quantity raised across sales; i.e., will likely be less than ICOVault's totalDeposited amount.

mapping (address => uint256) public balance: The amount of ether, in wei, received from any given investment address.

mapping (address => uint256) public sold: The number of VidyCoins, in the smallest possible subunit, transferred to any given investment address.

address public owner: The address of the contract owner. Some functions will revert if msg.sender is not this address.

uint256 public activationTime: The time, in seconds since the epoch, after which changes to its expirationTime are disallowed and purchases become possible.

uint256 public expirationTime: The time, in seconds since the epoch, at which the public sale ends; purchases will be disallowed.

address public token: The ERC20 token being distributed by this sale: the address of the VidyCoin contract.

address public vault: The Vault being used to store ether upon sale, and transfer ether upon returns: the address of the ICOVault contract.

function stock() view public returns (uint256 _count): Returns the number of VidyCoins currently held by the sale contract; equal to the VidyCoin.balanceOf(..) the sale contract.

function isGoalReached() view public returns (bool reached): Whether the ICO sales goal (soft cap) has been reached. Determined by examining the ICOVault's totalDeposited and goal.

Non-Constant Functions

function () public payable: Fallback function for receipt of ether. This is the purchase method: ether is transferred directly to the sale contract, which processes it as an attempted purchase. Only if the purchase results in the successful transfer of at least one subunit of VidyCoin to the purchaser is the ether accepted; otherwise, the transfer is reverted. The purchase is reflected in the sale's internal state (balance, sold, and amountRaised) and the ether itself is forwarded in full to the ICOVault at the address vault. The VidyCoins purchased are transferred by the sale contract to the purchasing address. Requirements: msg.sender is on the whitelist whose contract address was provided at construction, the ether received is sufficient to purchase a nonzero amount of VidyCoin, the VidyCoin balance of the sale contract is sufficient to transfer the appropriate amount to the purchaser, the execution block timestamp is between activationTime and expirationTime, the sale contract is on the ICOVault whitelist, and the ICOVault is in Active state and successfully receives the ether deposit.

function refund(uint256 _count) public returns (bool success): Initiates a refund of _count subunits of VidyCoin. The tokens are withdrawn from the caller's account using transferFrom; for this to succeed, the caller must have previously approved a withdrawal by the sale contract address. The equivalent value of ether, according to the sale price exchange rate, is transferred directly from the ICOVault to the investor's address (it does not pass through the sale contract). If _count is less than the number of VidyCoins purchased by the investor, a partial, pro-rated refund is provided. Rounded errors may result in a few lost wei if a partial refund is provided, but when the full total of VidyCoins is returned, the entire existing ether sale balance is returned (correcting for any rounding errors of earlier partial returns). The return is reflected in updates to the sale's state (balance, sold, amountRaised). Requirements: _count subunits of VidyCoin are sufficient to receive at least 1 wei worth of ether, the caller has a VidyCoin balance of at least _count and has approved a withdrawal of at least _count by the sale contract, the caller purchased at least _count worth of VidyCoin from this sale contract that has not previously been returned, the sale contract is on the ICOVault whitelist, the ICOVault is in Returning state and successfully transfers ether to the caller's account as a return.

function provideRefund(address _to, uint256 _count) public: Initiates a refund of _count subunits of VidyCoin to _to, as if requested by _to with refund(_count), except that the transfer of VidyCoins from _to to the sale contract will be omitted. Internal records still will be updated and ether returned from the vault. This function is a fallback for us to provide refunds to users who mistakenly transfered their VidyCoins to the sale contract (verifiable by examining the VidyCoin event log) rather than followed the approve -> transferFrom pattern; we intend for most, in not all, returns to be processed through the return(..) function. Requirements: _count subunits of VidyCoin are sufficient to receive at least 1 wei worth of ether, _to purchased at least _count worth of VidyCoin from this sale contract that has not previously been returned, the sale contract is on the ICOVault whitelist, the ICOVault is in Returning state and successfully transfers ether to the caller's account as a return.

function drainProducts(address _to, uint256 _count) public: Transfers _count subunits of VidyCoin from the sale contract to the address _to. Requirements: msg.sender == owner, the sale contract has at least _count subunits of VidyCoin.

function changeExternalWhitelist(address _address) public returns (bool success): Change the contract address referenced to determine if an address appears on the whitelist. The contract at _address should implement the Whitelist interface. Requirement: called by the contract's owner.

Implementation Notes

The SafeMath library used for all sale arithmetic is drawn from the openzeppelin-solidity suite: https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol. This library protects against integer overflow / underflow and in so doing prevents refund overdrafts. The Conversion library used for exchanges between ether and token quantities also relies on SafeMath.

Token price is expressed as a ratio -- the conversion rate from tokens to ether -- when both are expressed in the smallest possible subunit. For VidyCoin, this unit is unnamed; for ether it is "wei". Because both ether and VidyCoin use the same decimal notation (18 decimal places below whole numbers), this is equivalent to an exchange rate from VIDY to ETH. For example, with a price of 1 / 40000, 1 ETH will purchase 40,000 VIDY.

Pre-ICO Sale Contracts

The contracts used for the pre-ICO sale are extremely similar to those used in the public ICO, with only a few key differences. The whitelist contract has identical functionality to that used for the public sale, although the conditions for being whitelisted are different; the sale contract imposes a lock-up period before investors retrieve their VidyCoins rather than transferring them immediately.

The pre-ICO sale period is scheduled before the public ICO offering; the activationTime of the ICOVault corresponds to the start of the pre-ICO. The expirationTime of the pre-ICO sale occurs before the public sale activates, staggering the two sales in time. The pre-ICO sale offers VidyCoins at a better exchange rate than the public ICO, but only to an approved list of investors. Compared to the public offering, there are more stringent requirements for approval than simple KYC verification. The specific conditions for approval are established outside the blockchain and not fully expressed in the whitepaper.

VidyCoins purchased from the pre-ICO sale contract are retained by the sale contract itself until the lock-up period ends, which will be scheduled to occur after the end of the ICO (the expirationTime of ICOVault). If the ICO is successful, purchasers can retrive their VidyCoins from the sale at any time after the lock-up period ends. If the ICO is unsuccessful, the sale contract will process returns for the VidyCoins still held in lock-up (because they have not been transferred to the investor, there is no need for the investor to transfer any back).

Pre-ICO Sale Whitelist Contract

The pre-ICO is a private sale, open only to a whitelist of approved investors. The conditions for an address appearing on the whitelist are determined outside the blockchain, not by contract behavior or KYC verification. The list of investor addresses will be added to the whitelist during deployment according to the migration script; any additional investors approved during the pre-ICO sale period can be added to the whitelist as needed.

The whitelist, PreICOWhitelist, is only used to limit access to the sale contract's purchase function. Once VidyCoins are purchased by an address, removal of that address from the whitelist does not affect their ability to retrieve those tokens from the lock-up or initiate a refund if the ICO is unsuccessful.

Construction

The whitelist is constructed and configured by the migration script which iterates through a known list of approved investor addresses, adding them to the whitelist in batches. Once migration is complete the list is populated by the investor addresses who will make purchase during the pre-ICO. Contract ownership is retained by the deploying account in case additional investors must be manually added during the sale period.

Events

event WhitelistedAddressAdded(address indexed addr): Emitted when an address is added to the whitelist.

event WhitelistedAddressRemoved(address indexed addr): Emitted when an address is removed from the whitelist.

Fields and Constant Functions

mapping (address => bool) public whitelist: Whether the given address appears on the whitelist.

Non-Constant Functions

function addAddressToWhitelist(address _person) public: Add the provided address to the contract whitelist. Requirements: called by the contract owner.

function addAddressesToWhitelist(address[] _people) public: Add the provided addresses to the contract whitelist. Requirements: called by the contract owner.

function removeAddressFromWhitelist(address _person) public: Remove the provided address from the contract whitelist. Requirements: called by the contract owner.

function removeAddressesFromWhitelist(address[] _people) public: Remove the provided addresses from the contract whitelist. Requirements: called by the contract owner.

Pre-ICO Sale

The private sale contract PreICOSale holds a balance of VidyCoins, which it offers for sale to investors upon purchase via an ether transfer. The sale offers tokens according to a fixed exchange rate (price), which is the same for all customers of the sale. Upon purchase, the appropriate number of VidyCoins -- still held by the PreICOSale contract -- are recorded in a lock-up ledger within the sale contract itself. They are no longer reflected in the available stock and can neither be purchased by other investors, nor withdrawn by the contract owner. The transfer of those VidyCoins to the investor, assuming a successful ICO, is trustlessly guaranteed.

The sale contract keeps a record of the total amount of ether received from each investor address along with the number of VidyCoins purchased; if refunds are provided, a given investor can only receive refunds up to the amount received through the sale contract itself. In other words, if a single investor purchases VidyCoins from both the public sale and pre-ICO sale, they would need to request refunds from each to receive their full investment amount.

Purchases from the sale contract are made automatically upon receipt of ether via the contract's fallback method. The VidyCoins purchased are retained by the sale contract in lock-up, allowing later retrieval by the purchaser once the lock-up delay has been exceeded. If the ICO should fail to reach its goal (soft cap), investors can receive ether refunds for all unretrieved VidyCoins still in lock-up, and/or refund retrieved VidyCoins using the standard approve / transferFrom usage pattern. As a fallback for users who accidentally transfer their retrieved tokens back to the sale contract, the Vidy team can manually initiate a refund on that user's behalf (after verifying that their VidyCoins have been returned).

Pre-ICO Sale Interface

PreICOSale, like ICOVault, is Ownable and Schedulable. It also references an external whitelist contract, only allowing purchases from addresses that appear on that list. The VidyCoins it offers for sale are received via transfer from the VidyTeamWallet, which is also authorized to retrieve them (i.e., the VidyTeamWallet can cause the sale contract to transfer VidyCoins back to the wallet). VidyCoins that are being kept in lock-up cannot be retrieved by the VidyTeamWallet unless the ICO has failed and refunds are being issued.

Under normal circumstances, VidyCoin purchases are a two-step process. An investor makes a purchase by transferring ether to the sale contract; the contract records the appropriate quantity of VidyCoins in its lock-up. When the lock-up period ends, the investor retrieves their VidyCoins from the sale contract, causing an ERC20 transfer into their account. If the ICO is unsuccessful, investors can request a return of their invested ether, surrendering their purchased VidyCoins either from the lock-up or from their own wallet balance.

A few additional functions are available to the sale contract owner (the VidyTeamWallet) in case they are needed: lock-ups can be released early if the ICO is successful, or invalidated if the ICO ended without reaching its soft cap. Invalidating the lock-up allows the full balance of VidyCoins held by the sale to be withdrawn by the VidyTeamWallet, but since this is only possible if the ICO ends unsuccessfully, it does not affect the ability of investors to withdraw their purchased VidyCoins after a successful ICO.

Construction

The PreICOSale constructor sets the token sale price, a time for the token lock-up to release, and addresses for the ERC20 token, vault, and whitelist contracts. These values are immutable. Price is expressed as a ratio, a conversion rate from token to ether, when both are expressed in the smallest possible subunit.

After construction, the deployment script continues configuration of the sale. The sale is scheduled, setting its activationTime and expirationTime according to the whitepaper terms (the activationTime will correspond to that set on the ICOVault). Ownership of the sale is transferred to the VidyTeamWallet, and a transaction submitted to that wallet to transfer VidyCoins to the sale. Finally, the sale contract address is added to the ICOVault whitelist, causing the vault to accept deposits sent by the sale contract (which are made on behalf of investors).

Like all Schedulable contracts, the activationTime and expirationTime of the sale can be changed by the owner up to the moment the current activationTime is reached, as determined by examining the execution block timestamp. Once activationTime has been reached, the expirationTime becomes immutable. In other words, once the public sale period begins, the time at which it ends cannot be changed; similarly, the pre-ICO sale period is necessarily bounded by activation and expiration times of the ICOVault, since the vault must be in Active state to receive deposits.

Events

event Sale(address indexed to, uint256 price, uint256 count): Emitted when a purchase of at least 1 subunit of VidyCoin is made. price is the ether received, in wei; count is the number of VidyCoins provided, in the smallest possible subunit. i.e. a price of 1e18 is 1 ETH; a count of 1e18 is 1 VIDY.

event Refund(address indexed to, uint256 price, uint256 count): Emitted when at least 1 wei worth of VidyCoin is returned. price is the ether returned to the investor, in wei; count is the number of VidyCoins returned, in the smallest possible subunit. i.e. a price of 1e18 is 1 ETH; a count of 1e18 is 1 VIDY.

event OwnershipRenounced(address indexed previousOwner): Emitted when the current owner renounces ownership and the contract transitions permanently into a non-owned state.

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner): Emitted when the current owner transfers ownership to another address.

Fields and Constant Functions

uint256 public priceNumerator: The numerator of the ratio expressing a VidyCoin -> ether exchange rate. If the ratio is > 1, a VidyCoin is worth more than an ETH of ether.

uint256 public priceDenominator: The denominator of the ratio expressing a VidyCoin -> ether exchange rate. If the ratio is < 1, a VidyCoin is worth less than an ETH of ether.

uint256 public amountRaised: The total amount of ether, in wei, that has been raised by purchases from this sale. Does not reflect the total quantity raised across sales; i.e., will likely be less than ICOVault's totalDeposited amount.

mapping (address => uint256) public balance: The amount of ether, in wei, received from any given investment address.

mapping (address => uint256) public sold: The number of VidyCoins, in the smallest possible subunit, transferred to any given investment address.

address public owner: The address of the contract owner. Some functions will revert if msg.sender is not this address.

uint256 public activationTime: The time, in seconds since the epoch, after which changes to its expirationTime are disallowed and purchases become possible.

uint256 public expirationTime: The time, in seconds since the epoch, at which the public sale ends; purchases will be disallowed.

address public token: The ERC20 token being distributed by this sale: the address of the VidyCoin contract.

uint256 public lockupReleaseTime: The time, in seconds passed the epoch, at which VidyCoins held in lock-up are released to investors. Will be set to occur after the end of the ICO.

uint256 public lockupTokenTotal: The total quantity of VidyCoins, in the smallest possible subunit, held in lock-up: the amount purchased by investors, but not yet withdrawn or refunded.

mapping (address => uint256) public lockupTokenBalance: The number of VidyCoins, in the smallest possible subunit, held in lock-up for any given investment address (and not yet withdrawn or refunded).

bool public lockupReleasedEarly: Whether the contract owner has released VidyCoins in lock-up for retrieval by their purchasers. If released, they may be withdrawn immediately upon successful completion of the ICO, without waiting the additional time until lockupReleaseTime. The contract owner can only release VidyCoins from lock-up after the successful end of the ICO.

bool public lockupInvalidated: Whether the contract owner has invalidated the lock-up so that all VidyCoins held there can be drained from the sale contract. Lock-up invalidation does not affect the records of how many VidyCoins were purchased and held there, so refunds will still be honored by the contract. The contract owner can only invalidate the lock-up after the sale ends unsuccessfully and the lockup period has elapsed.

address public vault: The Vault being used to store ether upon sale, and transfer ether upon returns: the address of the ICOVault contract.

function stock() view public returns (uint256 _count): Returns the number of VidyCoins currently held by the sale contract, excluding those in lock-up; equal to the VidyCoin.balanceOf(..) the sale contract minus lockupTokenTotal. If lockupInvalidated, then equal to the VidyCoin.balanceOf(..) the sale contract.

function isGoalReached() view public returns (bool reached): Whether the ICO sales goal (soft cap) has been reached. Determined by examining the ICOVault's totalDeposited and goal.

Non-Constant Functions

function () public payable: Fallback function for receipt of ether. This is the purchase method: ether is transferred directly to the sale contract, which processes it as an attempted purchase. The ether received must be sufficient to successfully purchase at least 1 subunit of VidyCoin; otherwise, the transfer is reverted. The purchase is reflected in the sale's internal state (balance, sold, and amountRaised) and the ether itself is forwarded in full to the ICOVault at the address vault. The amount of VidyCoins purchase is recorded in the lock-up record (lockupTokenBalance and lockupTokenTotal); the tokens themselves are retained in the sale contract's account until later retrieval. Requirements: msg.sender is on the whitelist whose contract address was provided at construction, the ether received is sufficient to purchase a nonzero amount of VidyCoin, the VidyCoin balance of the sale contract excluding the amount in lock-up is sufficient to transfer the appropriate amount to the purchaser, the execution block timestamp is between activationTime and expirationTime, the sale contract is on the ICOVault whitelist, and the ICOVault is in Active state and successfully receives the ether deposit.

function refundLockUp() public returns (bool success): Initiates a refund of all ether sent by the caller to this sale contract. The ether is transferred directly from the ICOVault to the investor's address (it does not pass through the sale contract). The return is reflected in updates to the sale's state (balance, sold, amountRaised, lockupTokenBalance, and lockupTokenTotal); in particular, the caller's lockup balance is reduced to 0. There is no transfer of VidyCoins from the caller's balance to the sale contract; the refund is processed as a release of the VidyCoins already in the contract's lock-up. Requirements: the sale contract is on the ICOVault whitelist, the ICOVault is in Returning state and successfully transfers ether to the caller's account as a return.

function retrieve() public returns (bool success): Causes the sale contracts to transfer the amount of VidyCoins held in lock-up for the caller -- lockupTokenBalance[msg.sender] -- to the caller's address. The retrieval is reflected in the sale's lockup state (lockupTokenBalance, and lockupTokenTotal). Requirements: the lock-up period has ended or the contract owner has manually released the lock-up.

function refund(uint256 _count) public returns (bool success): Initiates a refund of _count subunits of VidyCoin. The tokens are withdrawn from the caller's account using transferFrom; for this to succeed, the caller must have previously approved a withdrawal by the sale contract address. The equivalent value of ether, according to the sale price exchange rate, is transferred directly from the ICOVault to the investor's address (it does not pass through the sale contract). If _count is less than the number of VidyCoins purchased by the investor, a partial, pro-rated refund is provided. Rounded errors may result in a few lost wei if a partial refund is provided, but when the full total of VidyCoins is returned, the entire existing ether sale balance is returned (correcting for any rounding errors of earlier partial returns). The return is reflected in updates to the sale's state (balance, sold, amountRaised). Requirements: _count subunits of VidyCoin are sufficient to receive at least 1 wei worth of ether, the caller has a VidyCoin balance of at least _count and has approved a withdrawal of at least _count by the sale contract, the caller purchased at least _count worth of VidyCoin from this sale contract that has not previously been returned, the sale contract is on the ICOVault whitelist, the ICOVault is in Returning state and successfully transfers ether to the caller's account as a return.

function provideRefund(address _to, uint256 _count) public: Initiates a refund of _count subunits of VidyCoin to _to, as if requested by _to with refund(_count), except that the transfer of VidyCoins from _to to the sale contract will be omitted. Internal records still will be updated and ether returned from the vault. This function is a fallback for us to provide refunds to users who mistakenly transfered their VidyCoins to the sale contract (verifiable by examining the VidyCoin event log) rather than followed the approve -> transferFrom pattern; we intend for most, in not all, returns to be processed through the return(..) function. Requirements: _count subunits of VidyCoin are sufficient to receive at least 1 wei worth of ether, _to purchased at least _count worth of VidyCoin from this sale contract that has not previously been returned, the sale contract is on the ICOVault whitelist, the ICOVault is in Returning state and successfully transfers ether to the caller's account as a return.

function releaseLockUp() public returns (bool success): Allows VidyCoins held in lock-up to be retrieved by their purchasers before the lock-up period has ended. The remaining conditions for retrieval still apply: the ICOVault reports that its goal (the soft cap) has been reached, and the execution block's timestamp occurs after expirationTime. Requirements: called by the sale contract's owner.

function invalidateLockUp() public returns (bool success): Invalidates the lock-up of all VidyCoins sold, although the records (lockupTokenBalance and lockupTokenTotal) are unaffected. This allows the owner to withdraw the full amount of VidyCoins held by the sale contract, including those previously in lock-up. This can only be done under conditions that necessarily imply an unsuccessful ICO, which are mutually exclusive with investors retrieving their tokens from lock-up. Refunds will still be honored whether the lock-up is invalidated or not. Requirements: called by the sale contract's owner, the execution block's timestamp occurs after lockupReleaseTime, the ICOVault is in Returning state.

function drainProducts(address _to, uint256 _count) public: Transfers _count subunits of VidyCoin from the sale contract to the address _to. Requirements: msg.sender == owner, the sale contract has at least _count subunits of VidyCoin that are not currently in lock-up (or the lock-up has been invalidated).

Implementation Notes

The SafeMath library used for all sale arithmetic is drawn from the openzeppelin-solidity suite: https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol. This library protects against integer overflow / underflow and in so doing prevents refund overdrafts. The Conversion library used for exchanges between ether and token quantities also relies on SafeMath.

Token price is expressed as a ratio -- the conversion rate from tokens to ether -- when both are expressed in the smallest possible subunit. For VidyCoin, this unit is unnamed; for ether it is "wei". Because both ether and VidyCoin use the same decimal notation (18 decimal places below whole numbers), this is equivalent to an exchange rate from VIDY to ETH. For example, with a price of 1 / 40000, 1 ETH will purchase 40,000 VIDY.