-
OSX or Linux
-
Python 3.5+ (Preferably from Homebrew if OSX)
-
solc
Storj tokens are based on Zeppelin StandardToken
ERC-20 contract. This contract has been further mixed in to include burn and upgradeable traits.
OpenZeppelin is pinned down to commit ffce7e3b08afad8d08a5fdbfbbca098f4d6cdf4e and solc
is pinned down to version 0.4.8.
500,000,000 tokens will be created on Storj Ethereum smaster wallet. This is the total supply of current Counterparty tokens.
These tokens are then allocated (using approve
) for
-
CounterParty -> Storj conversion server (based on the current Storj circulation)
-
Token sale (at the end of the sale)
-
Storj the company (held back in the master wallet)
Token distribution is exported as CSV and tokens allocated to their corresponding ETH addresses through an issuance script. Token sale accepts USD, BTC and ETH. Pricing and currency conversion mechanism for tokens can be decided later.
Storj will retain % tokens for the company and these are moved to a time locked vault (TODO, contract here).
Storj will burn % of their tokens.
Early investors may have their tokens also moved in time locked vaults.
Issuer contract and script is used to distribute token to retail token sale contributors.
Tokens are directly distributed from approve
pool given to the conversion server.
-
Counterparty user gives their Ethereum address
-
Server gives a Counterparty burn address where old tokens can be sent
-
When burn addresss is credited the conversion server does
transferFrom
and credits the given Ethereum address with the same amount of tokens
Install solc 0.4.8. This exact version is required. Read full paragraph how to install it on OSX.
Clone the repository and initialize submodules:
git clone --recursive [email protected]:Storj/storj-contracts.git
First install Python 3.5+:
brew install python3
Then in the repo folder we install Python dependencies in venv
:
cd storj-contracts
python3.5 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Then test solc:
solc --version
solc, the solidity compiler commandline interface
Version: 0.4.8+commit.60cc1668.Darwin.appleclang
Then test populus:
populus
Usage: populus [OPTIONS] COMMAND [ARGS]...
...
Compile:
populus compile
Output will be in build
folder.
Tests are written using py.test
in tests folder.
To run tests activate the virtual environment and then run:
py.test
Token balances are inputted as a CSV file with tuples (Ethereum address, balance).
Address entries must be unique - same Ethereum address cannot appear twice.
Example of CSV data:
address,amount
0x001d2A8b10F4b74852A70517C91Db2e924Fc9fc5,3.3
0x006dD316CA3131738396b15f82F11511b3313Bf4,4.4
Take update of scripts git repo
cd venv/src/ico ; git pull ; cd ../../..
Make sure you have Mainnet running in localhost:8545 or Kovan testnet running localhost:8547 (see populous.json
for port configuration).
Scripts will try to verify the deployed contract on EtherScan.io using Chrome, so you need to have chromedriver
browser automation utility installed.
brew install chromedriver
Make sure you have all contracts compiled to the latest version:
populus compile
Create an issuer Ethereum account on Parity. This is later referred as issuer account. Move some gas ETH there. You can do using geth console:
geth attach http://120.0.0.1:8547
personal.newAccount()
Unlock issuer account on Parity when starting parity on command line:
/usr/bin/parity --chain=kovan --unlock 0x72e0bdab1b4daccb9968a0e7bb1175dd629590e2 --unlock 0x001fc7d7e506866aeab82c11da515e9dd6d02c25 --password password.txt --jsonrpc-apis "web3,eth,net,parity,traces,rpc,personal"
Deploy the token contract (CentrallyIssuedToken
) with full urburnt balance and having the team multisig wallet as the balance owner and upgrade master. The team multisig wallet address is later referred as master address. Make sure the token contract has decimals value correctly set.
deploy-token --chain=kovan --address=[issuer account] --contract-name=CentrallyIssuedToken --name=Xtoken --symbol=XXX --supply=1000000 --decimals=8 --verify --verify-filename=CentrallyIssuedToken.sol --master-address=[team multisig wallet address]
Write down the token contract address.
Deploy an issuer contract using the following command. this is later referred as issuer contract.
distribute-tokens --chain=kovan --address=[issuer account] --token=[token contract address] --csv-file=dummy.csv --master-address=[team multisig address]
We can use the Gnosis multisig wallet by loading the STORJ Token abi and contract address. Call the approve function with the number of tokens allowed and the address of the account to issue tokens.
(Example using ipython console for a normal account. First start with ipython
and then paste in the text using %paste
command):
from populus import Project
from ico.utils import check_succesful_tx
# Unlock fake team multisig using geth console
fake_team_multisig_address = "0x72e0bdab1b4daccb9968a0e7bb1175dd629590e2"
token_address = "0x399fe67a232dd457c3639b3dccd64d5f7dcad187"
issuer_contract_address = "0x66b735baff9e4be524c555b61e3a20f0116a4527"
tokens_to_distribute = 500 * 10**8 # Use 8 decimals
project = Project()
with project.get_chain("kovan") as c:
web3 = c.web3
CentrallyIssuedToken = c.provider.get_base_contract_factory('CentrallyIssuedToken')
contract = CentrallyIssuedToken(address=token_address)
print("Fake team multisig ETH balance is", web3.eth.getBalance(fake_team_multisig_address))
print("Fake team multisig token balance is", contract.call().balanceOf(fake_team_multisig_address))
# We need to call approve() twice due to attack mitigation
txid = contract.transact({"from": fake_team_multisig_address}).approve(issuer_contract_address, 0)
check_succesful_tx(web3, txid)
txid = contract.transact({"from": fake_team_multisig_address}).approve(issuer_contract_address, tokens_to_distribute)
check_succesful_tx(web3, txid)
print("Approved", tokens_to_distribute)
Run the distribution script:
distribute-tokens --chain=kovan --address=[issuer account] --address-column=[Ethereum address column name in CSV] --amount-column=[Token amount column name in the CSV file] --csv-file=distribution.csv --issuer-address=[issuer contract] --no-allow-zero --limit=10000 --token=[token contract address] --master-address=[team multisig wallet address]
This script will start issuing tokens. In the case the script is interrupted you can start it again.
The number of tokens issued so far can be checked on Issuer contract address on etherscan.io.
This is step-by-step process how to create time vaults and how to put tokens in them. This assumes you have already created token (above).
Multiple vault contracts are needed to deploy, one for each time period.
For each vault, manually verify the sum of the tokens going in. The deployment script and contracts will check this multiple times.
TokenVault is controlled from an controller account that will pay the gas fees.
This will deploy the vault contract and verify it on EtherScan.
First we deploy a TokenVault contract using token-vault
tool for 3000 tokens. Decimal place conversion is calculated internally.:
token-vault --chain=kovan --address=[controlled account] --action=deploy --freeze-ends-at=1497746334 --tokens-to-be-allocated=3000 --token-address=[token address]
Write down the vault contract address. Also manually check it variables in Read contract view on EtherScan.
Now lets load vault data. The CSV amount column sum must match what we gave earlier in --tokens-to-be-allocated.
token-vault --chain=kovan --address=[controlled account] --action=load --token-address=[token account] --csv-file="dummy2.csv" --address-column="address" --amount-column="amount" --vault-address=[token vault address]
Example CSV:
address,amount
0x001d2A8b10F4b74852A70517C91Db2e924Fc9fc5,100
0x006dD316CA3131738396b15f82F11511b3313Bf4,100
0x72e0bdab1b4daccb9968a0e7bb1175dd629590e2,2800
Before we can lock the vault we need to move tokens-to-be-allocated
amount of tokens on the vault contract from the master account. Here is a Python example to move the tokens on the contract. You can also manually inspect using EtherScan that the token balance on the contract matches expected total amount:
from populus import Project
from ico.utils import check_succesful_tx
# Unlock fake team multisig using geth console
fake_team_multisig_address = "0x001FC7d7E506866aEAB82C11dA515E9DD6D02c25"
token_address = "0x2829aa40614901fc677aae4b090759d8fc660faf"
vault_address = "0xb9cdb05a6a4341ca72cfdc41f88a38c2755839a9"
tokens_to_locked = 3000 * 10**8 # Use 8 decimals
project = Project()
with project.get_chain("kovan") as c:
web3 = c.web3
CentrallyIssuedToken = c.provider.get_base_contract_factory('CentrallyIssuedToken')
TokenVault = c.provider.get_base_contract_factory('TokenVault')
contract = CentrallyIssuedToken(address=token_address)
vault = TokenVault(address=vault_address)
print("Fake team multisig ETH balance is", web3.eth.getBalance(fake_team_multisig_address))
print("Fake team multisig token balance is", contract.call().balanceOf(fake_team_multisig_address))
print("Transfering tokens to the vault")
txid = contract.transact({"from": fake_team_multisig_address}).transfer(vault_address, tokens_to_locked)
check_succesful_tx(web3, txid)
print("Tokens expected", vault.call().tokensToBeAllocated())
print("Tokens allocated", vault.call().tokensAllocatedTotal())
print("Tokens hold", vault.call().getBalance())
We lock the vault when the vault contract is holding the correct amount token, and we have manually inspected on EtherScan that the investor count, investor addresses and freeze ends at date are correct.
token-vault --chain=kovan --address=[controller account] --action=lock --token-address=[token address] --vault-address=[vault address]
Investors can claim the tokens from the vault calling the claim function.
from populus import Project
from ico.utils import check_succesful_tx
vault_address = "0xb9cdb05a6a4341ca72cfdc41f88a38c2755839a9"
claimer_account = "0x72e0bdab1b4daccb9968a0e7bb1175dd629590e2"
project = Project()
with project.get_chain("kovan") as c:
web3 = c.web3
CentrallyIssuedToken = c.provider.get_base_contract_factory('CentrallyIssuedToken')
TokenVault = c.provider.get_base_contract_factory('TokenVault')
vault = TokenVault(address=vault_address)
token_address = vault.call().token()
token = CentrallyIssuedToken(address=token_address)
print("Claiming tokens for the account", claimer_account)
before_balance = token.call().balanceOf(claimer_account)
txid = vault.transact({"from": claimer_account}).claim()
check_succesful_tx(web3, txid)
after_balance = token.call().balanceOf(claimer_account)
print("Claimed tokens", after_balance - before_balance)
In the case you load wrong amount of tokens on the vault you can claim them back to the controller account. This cannot be done for a locked vault.:
from populus import Project
from ico.utils import check_succesful_tx
# Unlock fake team multisig using geth console
fake_team_multisig_address = "0x001FC7d7E506866aEAB82C11dA515E9DD6D02c25"
token_address = "0x2829aa40614901fc677aae4b090759d8fc660faf"
vault_address = "0xb9cdb05a6a4341ca72cfdc41f88a38c2755839a9"
tokens_to_locked = 3000 * 10**8 # Use 8 decimals
project = Project()
with project.get_chain("kovan") as c:
web3 = c.web3
CentrallyIssuedToken = c.provider.get_base_contract_factory('CentrallyIssuedToken')
TokenVault = c.provider.get_base_contract_factory('TokenVault')
contract = CentrallyIssuedToken(address=token_address)
vault = TokenVault(address=vault_address)
print("Restoring tokens from faulted vault")
print("Vault owner", vault.call().owner())
before_balance = contract.call().balanceOf(fake_team_multisig_address)
txid = vault.transact({"from": fake_team_multisig_address}).recoverFailedLock()
check_succesful_tx(web3, txid)
after_balance = contract.call().balanceOf(fake_team_multisig_address)
print("Tokens recovered", after_balance - before_balance)
print("Tokens expected", vault.call().tokensToBeAllocated())
print("Tokens allocated", vault.call().tokensAllocatedTotal())
print("Tokens hold", vault.call().getBalance())