前回記事に引き続き、アリスのICOスマートコントラクトを読んでみることにします。今回は、クラウドセール実装部分について。
github : https://github.com/AlisProject/ico-contracts
クラウドセールは、その名の通りICOをする時に発行したトークンを売るためのロジックです。ソースコードは以下ですが、ここでもほとんどの機能がopenzeppelin-solidityから提供されているため、Alis側ではそれらをAlisトークン用に書き換える、という処理を行なっているようです。
https://github.com/AlisProject/ico-contracts/blob/master/contracts/AlisCrowdsale.sol
以下のように、openzeppelin-solidityからimportしているのでそれぞれについてみていってみます。
import 'zeppelin/contracts/crowdsale/CappedCrowdsale.sol';
import 'zeppelin/contracts/crowdsale/RefundableCrowdsale.sol';
import 'zeppelin/contracts/token/MintableToken.sol';
import 'zeppelin/contracts/lifecycle/Pausable.sol';
CappedCrowdsaleはCrowdsaleを継承しているので、Crowdsaleを先に見てみます。
https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v1.6.0/contracts/crowdsale/Crowdsale.sol
Crowdsaleは、セールを始める(トークンを発行する)と、トークンを買う、の部分を担当している様子。
セールを始める(トークンを発行する)部分はこちら。
function Crowdsale(uint256 _startTime, uint256 _endTime, uint256 _rate, address _wallet) public {
require(_startTime >= now);
require(_endTime >= _startTime);
require(_rate > 0);
require(_wallet != address(0));
token = createTokenContract();
startTime = _startTime;
endTime = _endTime;
rate = _rate;
wallet = _wallet;
}
最初4つはバリデーションです。
- クラウドセールの開始時間が未来であること
- セールの終了時間が開始時間よりも未来であること
- トークンのイーサリアムとの変換レートが正の数であること
- 集めたイーサリアムのアドレスが無効なものでないこと(address(0)はアドレスのセット忘れなどで生じる無効なアドレス)
その後、createTokenContract()でトークンを発行します。Crowdsale.solではここでMintableTokenを発行していますが、Alisトークンでは独自のトークンを発行するため、createTokenContract()はオーバーライドされています(後述)。
次に、トークンを買うはこちら。イーサリアムでトークンを購入します。
function buyTokens(address beneficiary) public payable {
require(beneficiary != address(0));
require(validPurchase());
uint256 weiAmount = msg.value;
// calculate token amount to be created
uint256 tokens = getTokenAmount(weiAmount);
// update state
weiRaised = weiRaised.add(weiAmount);
token.mint(beneficiary, tokens);
TokenPurchase(msg.sender, beneficiary, weiAmount, tokens);
forwardFunds();
}
送られて来たイーサリアムの数から、getTokenAmountでトークンの数を求めてその量をMintしていることがわかります。Mintについては前回の記事を参照してください。その後、送られて来たイーサリアムをforwardFunds()でウォレットに転送しています。
function forwardFunds() internal {
wallet.transfer(msg.value);
}
と、ここまでがCrowdsaleですが、これを継承しているCappedCrowdsaleを見て見ます。
この中ではcapという変数により、イーサリアムの上限を定めています。二つのフラグが書かれており、すでに上限に達しているかを確かめるためのhasEnded()と、
function hasEnded() public view returns (bool) {
bool capReached = weiRaised >= cap;
return capReached || super.hasEnded();
}
現在の支払いによりcapに達してしまうかどうか(現在の支払いが有効かどうか)を確かめるvalidPurchase()が書かれています。
function validPurchase() internal view returns (bool) {
bool withinCap = weiRaised.add(msg.value) <= cap;
return withinCap && super.validPurchase();
}
次に、RefundableCrowdsaleの処理を読んでいきます。Refundつまり返金の処理が書いてあります。クラウドセールなので目標額に達しなかった場合には返金をしっかりする、ということをあらかじめスマートコントラクトに書いておく、ということが大事になります。
この中のRefundableCrowdsaleからnewされているRefundVaultがポイントなのでそこについて詳しく見ていきます。RefundVaultは直訳で払い戻し金庫です。
中を見てもらえばわかるように、RefundVaultを呼び出す事で払戻し金庫としてのwalletの初期化と、クラウドセールのStateを設定しています。
StateがActiveなうち、つまりクラウドセール中にはdeposit(資金集め)、close(資金受付停止)、enableRefunds(refund可能なステータスに変更)というfunctionが実行可能で、
enableRefunds()によってState.Refundingになった場合に、実際にrefundが実行できます。investorのアドレスに対して、deposited[investor]の分だけ払い戻ししていることがわかります。
function refund(address investor) public {
require(state == State.Refunding);
uint256 depositedValue = deposited[investor];
deposited[investor] = 0;
investor.transfer(depositedValue);
Refunded(investor, depositedValue);
}
このRefundVaultをベースに作られているのが、RefundableCrowdsaleということになります。RefundableCrowdsaleはFinalizableCrowdsaleを継承していますが、これはクラウドセールが終了した時に呼び出されるfinalization()をオーバーライドするためです。
クラウドセールのゴールに達成していたら単純に資金集めを停止、達していなければrefund、という処理が書かれています。
function finalization() internal {
if (goalReached()) {
vault.close();
} else {
vault.enableRefunds();
}
super.finalization();
}
MintableTokenは前回の記事で紹介した通りです。
https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v1.6.0/contracts/lifecycle/Pausable.sol
ICOの緊急停止用のmodifierを定義しているようです。whenNotPaused()とwhenPaused()があり、これをmodifierとして使うことで何かやばいことが起きた時にICOを緊急停止できると。
ということで、やっとAlisCrowdsaleのimport部分の解読完了。
次回は、AlisCrowdsale.sol.solの独自実装部分を見ていきます。