
In this part 5 of a series, we will cover the DaicoPool contract that manages the funds raised.
Each TokenSale contract sends the funds to the DaicoPool contract when it is finalized. Thus, all the funds raised through the token sales will be pooled in this contract, and then it starts to release the funds by following the tap amount you set up before the sale.
Here is the constructor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* DaicoPool.sol */ // _votingTokenAddr = the token you issue = the address of the token for voting // tap_amount = the tap amount // _initialRelease = the amount of wei for your initial release(wei) constructor(address _votingTokenAddr, uint256 tap_amount, uint256 _initialRelease) public { require(_votingTokenAddr != 0x0); require(tap_amount > 0); //You can't have zero because the funds will be forever locked. initialTap = tap_amount; votingTokenAddr = _votingTokenAddr; status = Status.Initializing; initialRelease = _initialRelease; votingAddr = new Voting(ERC20Interface(_votingTokenAddr), address(this)); } |
You literally give the tap amount[wei/sec] to tap_amount
. The project owner should give the same amount they specified in their white paper. Since the tap amount is in public, you could lose the trust from investors if you give the different value to it.
By the same token, you specify the amount of wei for your initial release for your_initialRelease
.
Inside the constructor
, the Voting contract will be deployed and the tap amount and the self-destruction will be managed by the Voting contract.
State Transition
The DaicoPool contract has three states.
- Initializing(before the project is in progress)
The funds won’t be released yet. It receives the funds from the TokenSalecontract.
- ProjectInProgress(the project is in progress)
The funds release depending on the tap amount.
- Destructed(after the self-destruction)
This is the self-destructed state after the proposal of the self-destruction is passed. The funds won’t be released anymore, and token holders can get a refund.
Variables
These are variables in this contract.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/* DaicoPool.sol */ //the current tap amount[wei/sec] uint256 public tap; //the tap amount at the beginning[wei/sec] uint256 public initialTap; //the amount of the initial release funds uint256 public initialRelease; //the amount of funds released[wei]. it doesn't change after the withdrawal uint256 public releasedBalance; //the amount of funds withdrawn[wei] uint256 public withdrawnBalance; //the timestamp of the last time when releasedBalance was calculated uint256 public lastUpdatedTime; //the amount of funds raised(wei) uint256 public fundRaised; //the number of days that supports the project owner after the self-destruction(the tap amount * the number of days will be released) uint256 public closingRelease = 30 days; |
The Association with the TokenSaleManager
The DaicoPool contract associates itself with the TokenSaleManager with the setTokenSaleContract()
function in the DaicoPool contract when the TokenSaleManager contract is deployed. This setTokenSaleContract()
function cannot be executed more than once.
The TokenSaleManager will be able to trigger the release of the funds with the startProject()
function.
1 2 3 4 5 6 7 8 |
/* DaicoPool.sol */ function setTokenSaleContract(address _tokenSaleAddr) external { /* Can be set only once */ require(tokenSaleAddr == address(0x0)); require(_tokenSaleAddr != address(0x0)); tokenSaleAddr = _tokenSaleAddr; } |
Start The Release of The Funds
The TokenSaleManager contract executes the startProject()
function in the DaicoPool when it is finalized after the token sale.
This changes the state from Initializing
to ProjectInProgress
, which release the initial funds and start to release the tap depending on the amount you have set up.
1 2 3 4 5 6 |
/* DaicoPool.sol */ function startProject() external onlyTokenSaleContract { require(status == Status.Initializing); status = Status.ProjectInProgress; //change the state to <code class="markup--code markup--pre-code">ProjectInProgress</code> lastUpdatedTime = block.timestamp; //the update date of releaseBalance releasedBalance = initialRelease; //release the initial funds updateTap(initialTap); //set up the tap amount fundRaised = address(this).balance; //finalize the amount of funds raised through the sales } |
Withdrawal of The Funds
The project owner(the owner of the DaicoPool contract) can withdraw the funds released with the withdraw()
function as time passes.
The execution of the withdraw()
function will call updateReleasedBalance()
, which checks the balance of the funds that have been released already. Then the amount of funds available for your withdrawal(the amount that has been released – the amount that has been withdrawn) will be calculated with getAvailableBalance()
and you can withdraw up to that amount. You can withdraw from whichever the smaller amount of either the value in _amount
argument or maximum withdrawal amount.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* DaicoPool.sol */ function withdraw(uint256 _amount) public onlyOwner { require(_amount > 0); uint256 amount = _amount; updateReleasedBalance(); //update the balance of the funds that have been released already uint256 available_balance = getAvailableBalance(); //the amount of funds available for the withdrawal if (amount > available_balance) { amount = available_balance; // the withdrawal amount should not exceed the the amount of funds available for your withdrawal } withdrawnBalance = withdrawnBalance.add(amount); //update the amount that has been withdrawn owner.transfer(amount); //send emit WithdrawalHistory(“ETH”, amount); } |
There are three functions to check/update the balance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* DaicoPool.sol */ //update the amount that has been released function updateReleasedBalance() internal { releasedBalance = getReleasedBalance(); //calculate the amount that has been released with the tap lastUpdatedTime = block.timestamp; //update the calculation time } //the amount that has been released depending on the tap amount function getReleasedBalance() public view returns(uint256) { uint256 time_elapsed = block.timestamp.sub(lastUpdatedTime); //calculate the time passed return releasedBalance.add(time_elapsed.mul(tap)); //add (the tap amount * the time passed) to the amount that has been released } //calculate the amount of funds available for your withdrawal function getAvailableBalance() public view returns(uint256) { uint256 available_balance = getReleasedBalance().sub(withdrawnBalance); //the amount that has been released - the amount that has been withdrawn if (available_balance > address(this).balance) { available_balance = address(this).balance; //the withdrawal amount should not exceed the the amount of funds available for your withdrawal in the pool } return available_balance; } |
Update The Tap Amount
The DaicoPool could update the tap amount via the Voting contract. When the tap raising proposal is passed, the raiseTap()
function below is executed, and the tap amount will be updated.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/* DaicoPool.sol */ function raiseTap(uint256 tapMultiplierRate) external onlyVoting { updateReleasedBalance(); //update the amount that has been released already updateTap(tap.mul(tapMultiplierRate).div(100)); update the tap amount } //tapMultiplierRate = variation of the tap. the unit is the percentage(200 is 200% = twice) function updateTap(uint256 new_tap) private { //you can call only from inside the contract tap = new_tap; emit TapHistory(new_tap); } |
In the raiseTap()
function of the DaicoPool, you can either increase or decrease, but there is a limit in the variation in the Voting contract(101〜200%). This prevents the project owner from maliciously raising the tap and running away with the funds.
SelfDestruction
When the self-destruction proposal is passed, the Voting contract execute the selfDestruction()
function in DaicoPool, and self-destruct the pool. After the self-destruction, it releases the 30 days worth of the funds depending on the tap amount, and then close the tap. The token holders can get a refund after this.
1 2 3 4 5 6 7 8 9 10 11 |
/* DaicoPool.sol */ function selfDestruction() external onlyVoting { status = Status.Destructed; updateReleasedBalance(); releasedBalance.add(closingRelease.mul(tap)); //releases the 30 days worth of the funds updateTap(0); //close the tap uint256 _totalSupply = ERC20Interface(votingTokenAddr).totalSupply(); refundRateNano = address(this).balance.sub(getAvailableBalance()).mul(10**9).div(_totalSupply); //determine the rate to exchange with tokens } |
We release the 30 days worth of the funds for project owners to cover the cost of closing or shrinking the business. It requires some money to change the office, and other costs such as labor costs are not easy to cancel immediately.
Refund
If the pool were self-destructed, the token holders can get a refund in exchange with their tokens. The amount of refund follows the equation below:
The funds left in DaicoPool * the number of tokens you exchange / total supply
Before the refund, you need to give permission to the DaicoPool contract to spend on behalf of you with the approve()
function in the token contract.
1 2 3 |
function approve(address _spender, uint256 _value) external returns (bool); // _spender = the address that can spend tokens on behalf // _value = the amount of tokens to withdraw |
When you execute refund()
and return your tokens, you can get the refund depending on the amount of tokens you have sent.
1 2 3 4 5 6 7 8 9 10 |
/* DaicoPool.sol */ function refund(uint256 tokenAmount) external poolDestructed { require(ERC20Interface(votingTokenAddr).transferFrom(msg.sender, this, tokenAmount)); //withdraw the tokens from the executioner of the function uint256 refundingEther = tokenAmount.mul(refundRateNano).div(10**9); //calculate the amount to refund emit Refund(msg.sender, tokenAmount); msg.sender.transfer(refundingEther); //refund } // tokenAmount = the amount of tokens to exchange |
That was how the DaicoPool works 🙂 Thanks for reading!
Profile
Takuya Obata
Chief Operating Officer at ICOVO AG. And a blockchain engineer at the same time. He got MVP in Blockchain Hackathon Tokyo 2016. He is also experienced in natural language processing and chess like game programming.