PancakeswapのIFOから学ぶスマートコントラクト (Solidity)
仮想通貨やDefiなどをリサーチしていく中でスマートコントラクトという言葉をよく耳にすると思います。けれど、実際にどのような仕組みで動いているのかわからない方は多いと思います。
この記事はプログラミングが全くできない方でも、なんとなくで良いのでスマートコントラクトの仕組みを感じてもらえたらよいなと思って書いた記事です。
皆さん親しみのあるであろうPancakeswapを題材にしてみました。
そのなかでもIFOのコントラクトが読みやすそうだったので今回はIFO編です!
注)この記事にはコードがそこそこでてきます。アレルギーの方は気を付けてください
IFOとは
IFOは、Initial Farm OfferingのことでDEXを使って行われるトークンを使った資金調達です。PancakeswapのIFOの特徴は他のICOなどと異なり、トークン購入に必要な通貨はCAKE-BNBのLPトークンとなっています。
販売量が事前に決められており、予定よりも多くの資金が集まった場合には割合で配分されます。
例えば、販売量が10000のIFOに50000分LPが集まったとします。
予定の5倍資金が集まったのでユーザーは買いたい分の5分の1しか買うことができません。なので、50LPを入れても10LP分しか買えず、残りの40LPは返却されます。これが簡単なPancakeIFOの仕組みです。
ではここからコードを読んでいきます。
今回はgithubからPancake公式のコードを参照しています。
プチ前提
Solidityのコントラクトを大きく2つに分けると
・前半 コードの中で使う言葉の説明
・後半 実際に行う計算や処理
になっています。
前半 ~使う言葉の説明(定義)~
UserInfo
・いくらLPトークンを購入希望か
・トークンをclaimしたかどうか
この二つを記録しておく箱のようなものです。
コントラクト内で使われている変数の定義です。
・adminAddress
運営側のアドレスのことです。
・lpToken
CAKE-BNB のLPトークンです。
・OfferingToken
IFOで販売されるトークンです。
・startBlock
IFO開始のブロック数です。
・endBlock
IFO終了のブロック数です。
・raisingAmount
IFOで調達したいLPトークンの量です。 (例、10000LP)
・OfferingToken
IFOで販売されるIFOトークンの量です。 (例、 50,000,000)
・totalAmount
IFOで調達したLPトークンの総量です。
・mapping userInfo
アドレス名からユーザーの情報(いくら購入希望かなど)を取得する
・addressList
IFOに参加したアドレスのリスト
実際のIFOの画面と組み合わせるとこんな感じになります。
これは購入の際にLPトークンを入力する画面です。
deposit関数はまた後程出てくるのでお気にせず!
onlyAdmin adminだけができる処理だよ~
modifierは修飾子みたいなやつです。
requireは ( ) の中の条件が必須だよーという印
msg.senderは、関数を呼び出す人
msg.senderがadminAddressであることが必須条件なので、onlyAdminがついている関数はadminAddress(シェフのアドレス)でしか呼び出せないという事になります。これが次から説明する関数の中で登場してきます。
後半 ~実際に行う計算や処理~ (関数の定義)
ここからは関数についてみていきます。
紫色の文字は関数のニックネームです。どんな関数なのかがわかりやすくつけてあります。
setOfferingAmount IFOで配布する量を設定
setOfferingAmountは名前の通りofferingAmountを決めるものです。
さっきのTenetの例だと、100,0000 $TENがIFOで販売されるので
100,0000がofferingAmountになります。
配布量を決められるのはシェフだけなので先ほど説明したonlyAdminがついています。
require(必須条件)は
今のブロックナンバー < IFO開始のブロックナンバー
なので、IFO開始前にしか設定できないようになっています。
setRaisingAmount 希望調達量を設定
こちらはさっきと同じ要領で希望調達量を入力する関数です。
説明は省きます
deposit 資金をコントラクトに預ける
先ほど出てきたこの関数はユーザーがLPトークンをIFOのアドレスに送る関数です。
長いですがそんな難しくないので上から順にみていきます
function deposit(uint256 _amount) public {
amountは送りたいLPトークンの量です。
publicは誰でもこの関数を呼び出せることを示しています。
IFOは誰でも参加できるのでpublicになっています。
require (block.number > startBlock && block.number < endBlock, 'not ifo time');
require (_amount > 0, 'need _amount > 0');
require(必須条件)は
・IFO開催期間中
・LPトークンの量は0より大きい
です。日本語にすると当たり前ですね。
lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
msg.sender (ユーザー) => address.this (IFOのアドレス) にamount分送ります
if (userInfo[msg.sender].amount == 0) {
addressList.push(address(msg.sender));
userInfo[msg.sender].amountは、ユーザーが送ったLPの量です。
これが0の場合、つまり、ユーザーが最初にLPトークンを送ったときにそのアドレスがIFO参加者として追加されます。
userInfo[msg.sender].amount = userInfo[msg.sender].amount.add(_amount);
ユーザー情報に今回の取引で送られたトークンの量を追加してます。
totalAmount = totalAmount.add(_amount);
IFOのアドレスに集まったトークンの量も更新します。
emit Deposit(msg.sender, _amount);
最後にイベントを発生させます。
Solidity勉強ではない方は気にしなくても大丈夫です。
次の3つのget関数は、計算する上で必要なものなのでそんなに興味のない方は流して大丈夫です~~
getUseAllocation ユーザーのシェアを計算
これは送信したLPトークンの内、何パーセント分IFOトークンを買えるかを計算する関数です。
この計算結果が100000だと10%、1だと0.0001%になります。
Solidityでは小数点を計算することができないので大きい数を掛けることで小数点を消しています。
小数点をなくす余計な計算を抜かすと、ここでやっていることは
ユーザーが送ったLPトークン ÷ IFOに送られたLPトークンの総額
つまり、ユーザーのシェアを計算しています。
送ったトークンが1、総額が1000だった場合、割合は0.1%になります
getOffrringAmount ユーザーはいくら分IFOのトークンを買えたか計算
これはユーザーがいくらIFOトークンを買うことができたかを計算する関数です。
IFO側の希望調達量より多くのLPトークンが送られた場合、先ほどの関数でユーザーのシェアを計算し、総配布IFOトークンにシェア率を掛けます。
例だと、1,000,000 の 0.1% なので1000獲得できます。
getRefundAmount ユーザーに返金するLPトークンを計算
これは、LPトークンの総額が、希望調達額を上回ったときに、トークン購入に使わなかったLPトークンの量を求める関数です。
if (totalAmount <= raisingAmount) {
return 0;
希望調達量以下の場合は、送信したLPトークンがすべて使われるので返金額は0です。
uint256 allocation = getUserAllocation(_user);
uint256 payAmount = raisingAmount.mul(allocation).div(1e6);
return userInfo[_user].amount.sub(payAmount);
そうでない場合は返金が発生するので計算をします。
IFOトークンを買うのに使われたLPトークンを、送られたLPトークンの量から引いています。
harvest トークンを回収する
次はハーヴェストの際の関数です!
こっちもちょっと長いけど頑張ってみていきましょう
require (block.number > endBlock, 'not harvest time');
require (userInfo[msg.sender].amount > 0, 'have you participated?');
require (!userInfo[msg.sender].claimed, 'nothing to harvest');
ここでは
・IFOが終了しているか?
・ユーザーはLPトークンを送っているか?
・ユーザーはまだトークンをclaimしていないか
の3点を確認しています。
uint256 offeringTokenAmount = getOfferingAmount(msg.sender);
uint256 refundingTokenAmount = getRefundingAmount(msg.sender);
受け取れるIFOトークンの量と、返金されるLPの量を求めてます
offeringToken.safeTransfer(address(msg.sender), offeringTokenAmount);
IFOトークンをユーザーへ送信する。
if (refundingTokenAmount > 0) {
lpToken.safeTransfer(address(msg.sender), refundingTokenAmount);
返金がある場合、LPトークンも送信する
userInfo[msg.sender].claimed = true;
ユーザーがハーヴェスト済みと記録しておく。
emit Harvest(msg.sender, offeringTokenAmount, refundingTokenAmount);
イベントの発生
finalWithdraw adminがトークンを引き出す
adminがIFOで調達した資金を引き出すための関数です。
require (_lpAmount < lpToken.balanceOf(address(this)), 'not enough token 0');
require (_offerAmount < offeringToken.balanceOf(address(this)), 'not enough token 1');
入力した量がアドレス内の量より少ないかを確認
アドレスにはいいていない量のトークンは引き出せないからね・・・
lpToken.safeTransfer(address(msg.sender), _lpAmount);
offeringToken.safeTransfer(address(msg.sender), _offerAmount);
LPトークンとIFOトークンのあまり(ある場合は)をアドレスへ送信
以上!!
まあざっとこんな感じです!w
スマートコントラクトの雰囲気が伝わればよいなと思います。
このコントラクトがブロックチェーン上に刻まれて誰にも改竄できない状態になるので私たちは安心してトークンを預けることができるというわけです。
このコントラクトの中に、こっそりadminがトークンを引き出せる関数などが混じっていたら預けたトークンを抜き取られてしまいます!
そういう悪いことができないようにコントラクトの監査もそんざいしているんですね。たぶん・・・・笑
おまけ
記事を読んでさらにブロックチェーンに興味を持って本格的に開発やSolidityなど学んでみたい・・・と思った方!
私が今インターンしているBlockBaseではイーサリアムの開発を基礎から学ぶ勉強会を開催しています!Disocordのlearningページでかいさいしているので参加してみてください!
https://discord.gg/vy4sNt5vTE
ではまたいつか!!人気かやる気があったらSwapやFarm、他のDefiのプロジェクト編もやってみようと思います。
この記事が気に入ったらサポートをしてみませんか?