BAT(Basic Attention Token)はEthereumのスマートコントラクトを利用したトークンです。
ソースコードはsolidityというJSに似た言語で書かれており200行程度です。
- solidityを学びたい人
- solidity開発者
- BATについて知りたい開発者
- トークンの仕組みをしりたい開発者
がターゲットです。
BATとは
BATとは広告主とパブリッシャーとユーザーをつなぐスマートコントラクトです。
広告料の支払い等をスマートコントラクトで実行し、広告効果の計測等は外部のシステムで行います。
なのでsolidityのコードには送金に関わるものしかありません。
BATに関する詳しいことは【BAT】Basic Attention Token(ベーシック・アテンション・トークン)に乗ってます。Google翻訳っぽい日本語ですがホワイトペーパーよりも簡潔なのでおすすめです。
solidityのコード
基本的に全行コメントしてあります。わからない部分はコメントを頂ければ書き足します。
pragma solidity ^0.4.10;
/* SafeMath
四則演算で予想外の結果が帰る場合は例外を投げるメソッド集。
*/
contract SafeMath {
/* safeAdd()
オーバーフローしないことを確認した上で足し算をする。
*/
/* Tip: internal
オブジェクト指向言語のprotectedと同じ概念。
継承先のcontract内からはアクセスできる。
contractの外からはアクセスできない。
*/
/* Tip: assert
assert(false)となった場合に残りのgasを使い切った上でrevertする
*/
function safeAdd(uint256 x, uint256 y) internal returns(uint256) {
uint256 z = x + y;
assert((z >= x) && (z >= y));
return z;
}
/* safeSubtract()
負の値にならないことを確認した上で引き算をする。
*/
function safeSubtract(uint256 x, uint256 y) internal returns(uint256) {
assert(x >= y);
uint256 z = x - y;
return z;
}
/* safeMult()
変なことが起きないことを確認した上で掛け算する。
*/
function safeMult(uint256 x, uint256 y) internal returns(uint256) {
uint256 z = x * y;
assert((x == 0)||(z/x == y)); /* TODO: なにを確認してるんだろう */
return z;
}
}
/* Token
ERC 20の仕様に則った抽象クラス
*/
contract Token {
uint256 public totalSupply;
function balanceOf(address _owner) constant returns (uint256 balance);
function transfer(address _to, uint256 _value) returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
function approve(address _spender, uint256 _value) returns (bool success);
function allowance(address _owner, address _spender) constant returns (uint256 remaining);
/* Transferメソッドが実行されたときにログを残す */
event Transfer(address indexed _from, address indexed _to, uint256 _value);
/* Approvalメソッドが実行されたときにログを残す */
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
/* StandardToken
ERC 20の仕様に則ったクラス
*/
contract StandardToken is Token {
/* transfer()
コントラクト実行主から他アドレスへ送金する
*/
function transfer(address _to, uint256 _value) returns (bool success) {
/* 送金主の残高が足りているか確認 */
if (balances[msg.sender] >= _value && _value > 0) {
/* 送金主の残高から取引額を差し引く */
balances[msg.sender] -= _value;
/* 送金先の残高へ取引額を足す */
balances[_to] += _value;
/* 送金イベントを発火 */
Transfer(msg.sender, _to, _value);
return true;
} else {
return false;
}
}
/* transferFrom()
特定のアドレスから他アドレスへ送金する
*/
function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
/*
送金主の残高が足りているか確認
コントラクト実行主が送金の権限を持っているかを確認
*/
if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) {
/* 送金先の残高へ取引額を足す */
balances[_to] += _value;
/* 送金主の残高から取引額を差し引く */
balances[_from] -= _value;
/* 実行主が持つ送金権限の残高から取引額を差し引く */
allowed[_from][msg.sender] -= _value;
/* 送金イベントを発火 */
Transfer(_from, _to, _value);
return true;
} else {
return false;
}
}
/* balanceOf()
特定のアカウントの残高を確認する
*/
function balanceOf(address _owner) constant returns (uint256 balance) {
return balances[_owner];
}
/* approve()
コントラクト実行主の残高を_value分だけ引き出す権限を_spanderに付与する
*/
function approve(address _spender, uint256 _value) returns (bool success) {
/* 承認確認イベントを発火 */
allowed[msg.sender][_spender] = _value;
/* 承認イベントを発火 */
Approval(msg.sender, _spender, _value);
return true;
}
/* allowance()
_ownerの残高を_value分だけ引き出す権限を_spanderが持っているか確認する
*/
function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
return allowed[_owner][_spender];
}
/* 構造体balancesを定義 */
mapping (address => uint256) balances;
/* 構造体allowedを定義 */
mapping (address => mapping (address => uint256)) allowed;
}
/* BAToken
トークン本体のcontractの実装
*/
contract BAToken is StandardToken, SafeMath {
/* メタデータ */
/* コントラクト名 */
string public constant name = "Basic Attention Token";
/* コントラクトシンボル */
string public constant symbol = "BAT";
/* コントラクトの小数点以下の桁数 */
uint256 public constant decimals = 18;
/* コントラクトのバージョン */
string public version = "1.0";
/* 運営用のETHを貯めておくアドレス */
address public ethFundDeposit;
/* 運営用のBATを貯めておくアドレス */
address public batFundDeposit;
/* クラウドセール用変数 */
/* クラウドセールが終わったかどうか */
bool public isFinalized;
/* クラウドセールが始まるブロック */
uint256 public fundingStartBlock;
/* クラウドセールが終わるブロック */
uint256 public fundingEndBlock;
/* 最初に運営側にデポジットするトークンの量 */
uint256 public constant batFund = 500 * (10**6) * 10**decimals;
/* 6400BATトークンを1ETHと交換する */
uint256 public constant tokenExchangeRate = 6400;
/* 作るBATトークン数の上限 */
uint256 public constant tokenCreationCap = 1500 * (10**6) * 10**decimals;
/* 作るBATトークン数の下限 */
uint256 public constant tokenCreationMin = 675 * (10**6) * 10**decimals;
/* LogRefundメソッドが実行されたときにログを残す */
event LogRefund(address indexed _to, uint256 _value);
/* CreateBATメソッドが実行されたときにログを残す */
event CreateBAT(address indexed _to, uint256 _value);
/* BAToken()
コンストラクタ
*/
function BAToken(
address _ethFundDeposit,
address _batFundDeposit,
uint256 _fundingStartBlock,
uint256 _fundingEndBlock)
{
/* クラウドセールが終わったかどうか */
isFinalized = false;
/* 運営用のETHを貯めておくアドレス */
ethFundDeposit = _ethFundDeposit;
/* 運営用のBATを貯めておくアドレス */
batFundDeposit = _batFundDeposit;
/* クラウドセールを開始するブロック */
fundingStartBlock = _fundingStartBlock;
/* クラウドセールを終了するブロック */
fundingEndBlock = _fundingEndBlock;
/* 運営側にデポジットする分だけ、配ったことにする */
totalSupply = batFund;
/* 運営側にデポジット */
balances[batFundDeposit] = batFund;
/* BATの生成をログ */
CreateBAT(batFundDeposit, batFund);
}
/* createTokens()
BATトークンを生成する
*/
function createTokens() payable external {
/* クラウドセールが終了していれば例外を投げる */
if (isFinalized) throw;
/* fundingStratBlockより今のblockが古ければ例外を投げる */
if (block.number < fundingStartBlock) throw;
/* fundingEndBlockより今のblockが新しければ例外を投げる */
if (block.number > fundingEndBlock) throw;
/* メッセージと共に送られたvalueが0であれば例外を投げる */
if (msg.value == 0) throw;
/* tokens = msg.value * tokenExchangeRate */
uint256 tokens = safeMult(msg.value, tokenExchangeRate);
/* checkedSupply = totalSupply + tokens */
uint256 checkedSupply = safeAdd(totalSupply, tokens);
/* BATトークン作成の上限を超えていれば例外を投げる */
if (tokenCreationCap < checkedSupply) throw;
/* 作ったBATトークンの総量を更新 */
totalSupply = checkedSupply;
/* メッセージ主にBATトークンを付与 */
balances[msg.sender] += tokens;
/* BATトークンの作成をログ */
CreateBAT(msg.sender, tokens);
}
/* finalize()
クラウドセールを終了する
*/
function finalize() external {
/* クラウドセールが終了していれば例外を投げる */
if (isFinalized) throw;
/* メッセージの送り主がethFundDepositでなければ例外を投げる */
if (msg.sender != ethFundDeposit) throw;
/* 作ったBATトークンの総量がtokenCreationMinを超えてなければ例外を投げる */
if(totalSupply < tokenCreationMin) throw;
/* fundingEndBlockより今のblockが古く */
/* 作ったBATトークンの総量がtokenCreationCapと等しくなければ例外を投げる */
if(block.number <= fundingEndBlock && totalSupply != tokenCreationCap) throw;
/* isFinializeにtrueをセット(クラウドセール終了) */
isFinalized = true;
/* ethFundDepositにthis.balance(ETH)を送金 */
if(!ethFundDeposit.send(this.balance)) throw;
}
/* refund()
なにか失敗した場合に集めたETHを全額運営に送金する
*/
function refund() external {
/* クラウドセールが終了していれば例外を投げる */
if(isFinalized) throw;
/* fundingEndBlockより今のblockが古ければ例外を投げる */
if (block.number <= fundingEndBlock) throw;
/* 作ったBATトークンの総量がtokenCreationMinを超えていれば例外を投げる */
if(totalSupply >= tokenCreationMin) throw;
/* メッセージの送り主がethFundDepositでなければ例外を投げる */
if(msg.sender == batFundDeposit) throw;
/* メッセージの送り主(ethFundDeposit)の所有トークンを代入 */
uint256 batVal = balances[msg.sender];
/* トークンを所有してなければ例外を投げる */
if (batVal == 0) throw;
/* 所有トークンを0にする */
balances[msg.sender] = 0;
/* tokenSupply = totalSupply - batVal */
totalSupply = safeSubtract(totalSupply, batVal);
/* batValをETH換算に変換 */
uint256 ethVal = batVal / tokenExchangeRate;
/* refundをログ */
LogRefund(msg.sender, ethVal);
/* メッセージの送り主にethVal(ETH)送金 */
if (!msg.sender.send(ethVal)) throw;
}
}
次はThe DAOのソースコードを読もうかな。