ガチャを回すとNFTがランダムで当たるようなスマートコントラクトを書いたのでメモがてらQiitaにも書き殴ろうと思います。
概要
今回は独自コインであるERC20トークンを使うと1回ガチャができるようにします。
ガチャで当たるトークンはERC1155のトークンで4種類にします。
使うチェーンはETHのテストネット、Rinkebyです。fakeのETHをfaucet からもらってきてくださいね。
今回は確率は均一にします。
「トークンのレアリティを設計して、レアなトークンは確率を下げる」という実装も応用してやろうと思えばできますね。
そういえば 前書いた記事 でERC20トークンの実装やrinkeby、faucetについては書いているので今回はスキップします。同じ内容ですのでそちらを参照してください。
コード
ERC1155トークン & ガチャ
// SPDX-License-Identifier: MIT LICENSE
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
pragma solidity ^0.8.0;
contract Emaki is ERC1155, Ownable {
string baseMetadataURIPrefix;
string baseMetadataURISuffix;
using Strings for uint256;
uint256 randNonce = 0;
struct TokenInfo {
IERC20 paytoken;
uint256 costvalue;
}
uint256 degit_18 = 1000000000000000000
address LWZaddress = 0x...的なデプロイしたERC20トークンのアドレスを入れる;
IERC20 public LWZ = IERC20(LWZAddress);
TokenInfo public AllowedCrypto = {
LWZ,10*degit_18
};
uint256[] public tokens = [
0, 1, 2, 3
];
string public name;
string public symbol;
constructor() ERC1155("") {
name = "LowzzyGacha";
symbol = "LGacha";
}
function uri(uint256 _id) public view override returns (string memory) {
return string(abi.encodePacked(
baseMetadataURIPrefix,
Strings.toString(_id),
baseMetadataURISuffix
));
}
# ガチャを回すときはこの関数を呼び出します。
# _amount : 何回ガチャを回すか
# requireは例外処理の記述で、rails でいうところのraiseです。
function gacha(uint256 _amount) public payable {
TokenInfo storage tokens = AllowedCrypto;
IERC20 paytoken;
paytoken = tokens.paytoken;
uint256 cost;
cost = tokens.costvalue * _amount;
uint256 allowCost = paytoken.allowance(msg.sender,address(this));
require(allowCost >= cost * _amount, "Not enough balance to complete transaction");
paytoken.transferFrom(msg.sender, address(this), cost);
uint256 num;
uint256 id;
for (uint256 i = 1; i <= _amount; i++) {
randomNum = getRandomNum(msg.sender);
uint256 len = tokens.length;
uint256 id = randomNum % len;
uint256 mintAmount = 1;
_mint(msg.sender, id, mintAmount, "");
}
}
# 今回の実装のキモである擬似乱数生成部分。
# ブロックチェーンはその性質上乱数は予測しやすいため、
# 十分に注意して生成する必要があります。
# keccak 256を用いてtransactionやblockの情報から擬似乱数を生成しています。
# ガチャが同時に複数回回された時、_to、block.timestampは同じ数値が入ってしまうため
# randNonceを用いて同じ数値が入らないようにしています。
function getRandomNum(address _to) public returns (uint256){
randNonce++;
uint256 randomNum = uint256(
keccak256(
abi.encode(
_to,
block.timestamp,
randNonce
)
)
);
return randomNum;
}
# ガチャをする際に支払われたトークンはスマートコントラクトのアドレスに
# 貯まっていきます。
# この関数はそのコントラクトに貯まったERC20トークンを引き出す関数です。
function withdraw() public payable onlyOwner() {
TokenInfo storage tokens = AllowedCrypto;
IERC20 paytoken;
paytoken = tokens.paytoken;
paytoken.transfer(msg.sender, paytoken.balanceOf(address(this)));
}
}
デプロイ
まず初めにERC20トークンをデプロイします。
前回と同じようにremixを使います。
デプロイしたら、ERC20トークンのコントラクトアドレスを
address LWZaddress = 0x...的なデプロイしたERC20トークンのアドレスを入れる;
に入れてみてください。
入れたらガチャスマートコントラクトをデプロイしてください。
ガチャトークンをデプロイしたら、erc20トークンであるLWZをガチャスマートコントラクトにapproveします。
その後、gacha(1)とでもすれば1回ガチャが回せますね。
ちょいと眠いので一旦おわり。最後の方が雑になってしまったので今度加筆修正をしますね!
それでは!