注意!Cairo 言語の変更により、このチュートリアルは古くなっています。最新の 1.10 および構文については、こちらの文を参照してください https://starknetastro.xlog.app/Starknet_Shanghai_Workshop_DAY1#
概要#
本コースは、迅速に習得できる BootCamp として、Cairo 言語の特性について深く説明することはありません(ただし、最後のセッションでは Cairo 言語の他の有用な部分について少し詳しく紹介します)。
Cairo を事前に深く学びたい場合は、最近翻訳したCairo-Book 日本語版をご覧ください。
環境設定#
最小インストールオプション:
このコースでは、前のセッションで要求された Cairo 1.0 と、Starknet の Cairo 0.x CLI に必要なすべての依存関係がインストールされていることが求められます。
注意!DevNet のアップグレードにより、現在はstarknet-compile 1.1.0
のみがサポートされていますので、Cairo 1.0 を v1.1.0 にアップグレードする必要があります。
Cairo をインストールしたディレクトリで以下のコマンドを実行してください:
git checkout main
git pull
cargo build --all --release
また、前回のセッションのフィードバックに基づき、多くの学生が python をインストールした後も正常にコンパイルできないことがあるため、ここでいくつかの一般的な問題の解決方法を示します:
linker 'cc' not found
、cc のリンカーが見つからない場合は、以下の方法で解決できます。
sudo apt install build-essential
もちろん、万が一に備えて、以下の一式を直接インストールすることもできます:
sudo apt-get install build-essential libncursesw5-dev libgdbm-dev libc6-dev zlib1g-dev libsqlite3-dev tk-dev libssl-dev openssl libbz2-dev libreadline-dev
ModuleNotFoundError: No module named '_ctypes'
解決策は少し面倒で、python を再インストールする必要があります。
pyenv uninstall 3.9.16
sudo yum install libffi-devel
pyenv install 3.9.16
use_2to3 is invalid.
時々、理由もなく発生します。
pip install -U setuptools
CLI のインストール#
操作を簡単にするために、まず Cairo 1.0 のルートディレクトリに移動します(ここでは、前回のセッションで 1.0 をインストールしたディレクトリを参照してください。私の例は以下の通りです)。
cd ~/cairo/
test フォルダを作成します:
mkdir -p starknetastro/camp1_lesson2/
cd starknetastro/camp1_lesson2/
次に、python の仮想環境を作成します:
python3.9 -m venv venv
仮想環境を起動します:
source venv/bin/activate
この時点で、ターミナルの前に(venv)が表示されるはずです。CLI をインストールしましょう。
(venv) camp1 $ pip install cairo-lang
インストールが成功したかどうかを確認します。
(venv) camp1 $ starknet --version
ここで出力されるべき内容は:
starknet 0.11.2
Starknet テストネットアカウントの設定#
次に、テストネットアカウントを設定します。まず、以下の環境変数を定義します:
# テストネットを指定
export STARKNET_NETWORK=alpha-goerli
# デフォルトのウォレット実装を設定
export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount
# コンパイラのパスをCairo 1のパスに指定(自分のパスに合わせて修正してください)
export CAIRO_COMPILER_DIR=~/cairo/target/release/
その後、この内容を.zshrc
または.bashrc
に保存して、ターミナルを閉じた後でも環境変数を使用できるようにします。方法についてはここでは詳しく説明しません。
以下の 2 つのコマンドを使用してテストします。
(venv) $ $CAIRO_COMPILER_DIR/cairo-compile --version
cairo-compile 1.1.0
(venv) $ $CAIRO_COMPILER_DIR/starknet-compile --version
starknet-compile 1.1.0
両方とも 1.1.0 を出力すれば、環境設定は正しいです。
テストネットのウォレットを作成#
次に、テストネット上にウォレットを作成します:
starknet new_account --account StarknetAstro
ここで出力されるべき内容は
Account address: 0xあなたのアドレス
Public key: 0xあなたの公開鍵
適切な金額をアカウントに移動し、その後、'starknet deploy_account'コマンドを実行してアカウントをデプロイします。
注意:これはOpenZeppelinアカウントコントラクトの修正バージョンです。署名の計算方法が異なります。
次に、テストネットにデプロイする必要があります。Starknet でのデプロイもトランザクションの一部であるため、ガスが必要です。クロスチェーンブリッジまたは公式 Faucetを通じて0xあなたのアドレス
にいくつかのテスト用 eth を送信します。
次に、このコマンドを使用してデプロイします。
starknet deploy_account --account StarknetAstro
出力:
Sending the transaction with max_fee: 0.000114 ETH (113796902644445 WEI).
Sent deploy account contract transaction.
Contract address: 0xあなたのアドレス
Transaction hash: 0xトランザクションアドレス
デプロイのテスト#
まず、テストコントラクトを使用して正常にデプロイできるかどうかを試してみましょう。
以下のコマンドを実行します:
mkdir src sierra
touch src/example.cairo
次に、src/example.cairo
にテスト内容を記入します:
#[contract]
mod SimpleStorage {
#[starknet::storage]
struct Storage {
}
}
次に、cairo ファイルを sierra ファイルにコンパイルします:
$CAIRO_COMPILER_DIR/starknet-compile src/example.cairo sierra/example.json
コンパイルが成功したら、コントラクトの ClassHash を宣言します:
starknet declare --contract sierra/example.json --account StarknetAstro
もちろん、同じ ClassHash は複数回宣言できないため、このテスト用コントラクトはすでに宣言されているため、すでに宣言されているというエラー出力が表示されますが、これは問題ありません:
Got BadRequest while trying to access https://alpha4.starknet.io/feeder_gateway/simulate_transaction?blockNumber=pending&skipValidate=false. Status code: 500;
text: {"code": "StarknetErrorCode.CLASS_ALREADY_DECLARED",
"message": "Class with hash 0x695874cd8feed014ebe379df39aa0dcef861ff495cc5465e84927377fa8e7e6
is already declared.
0x317d3ac2cf840e487b6d0014a75f0cf507dff0bc143c710388e323487089bfa != 0”}.
次に、コントラクトのインスタンスを実際にデプロイします:
starknet deploy --class_hash 0x695874cd8feed014ebe379df39aa0dcef861ff495cc5465e84927377fa8e7e6 --account StarknetAstro
出力は以下の通りです:
Sending the transaction with max_fee: 0.000132 ETH (132082306595047 WEI).
Invoke transaction for contract deployment was sent.
Contract address: 0x060e17c12d4e3fee8af2e28e6a310a3192a1b1190d060cb4324e234213d72b64
Transaction hash: 0x73f995d1fd9a05161e271f5ffb879bd70b79185a2b3de5536a289780387dd30
これにより、実際にデプロイが成功したことが示されます!ここで私たちがデプロイしたコントラクトを見ることができます。
ERC20 コードテンプレート#
以下の erc20.cairo を上記で作成したsrc
に保存し、ターミナル操作を便利にします。
ファイル名:erc20.cairo
use starknet::ContractAddress;
#[starknet::interface]
trait IERC20<TStorage> {
fn name(self: @TStorage) -> felt252;
fn symbol(self: @TStorage) -> felt252;
fn decimals(self: @TStorage) -> u8;
fn totalSupply(self: @TStorage) -> u256;
fn balanceOf(self: @TStorage, account: ContractAddress) -> u256;
fn allowance(self: @TStorage, owner: ContractAddress, spender: ContractAddress) -> u256;
fn transfer(ref self: TStorage, recipient: ContractAddress, amount: u256);
fn transferFrom(
ref self: TStorage, sender: ContractAddress, recipient: ContractAddress, amount: u256
);
fn approve(ref self: TStorage, spender: ContractAddress, amount: u256);
fn increase_allowance(ref self: TStorage, spender: ContractAddress, added_value: u256);
fn decrease_allowance(ref self: TStorage, spender: ContractAddress, subtracted_value: u256);
}
#[contract]
mod ERC20 {
use zeroable::Zeroable;
use starknet::get_caller_address;
use starknet::contract_address_const;
use starknet::ContractAddress;
#[starknet::storage]
struct Storage {
name: felt252,
symbol: felt252,
decimals: u8,
totalSupply: u256,
balances: LegacyMap::<ContractAddress, u256>,
allowances: LegacyMap::<(ContractAddress, ContractAddress), u256>,
}
#[event]
fn Transfer(from: ContractAddress, to: ContractAddress, value: u256) {}
#[event]
fn Approval(owner: ContractAddress, spender: ContractAddress, value: u256) {}
#[constructor]
fn constructor(
ref self: Storage,
name: felt252,
symbol: felt252,
decimals: u8,
initialSupply: u256,
recipient: ContractAddress
) {
self.name.write(name);
self.symbol.write(symbol);
self.decimals.write(decimals);
assert(!recipient.is_zero(), 'ERC20: mint to the 0 address');
self.totalSupply.write(initialSupply);
self.balances.write(recipient, initialSupply);
}
#[external]
impl IERC20Impl of super::IERC20<Storage> {
fn name(self: @Storage) -> felt252 {
self.name.read()
}
fn symbol(self: @Storage) -> felt252 {
self.symbol.read()
}
fn decimals(self: @Storage) -> u8 {
self.decimals.read()
}
fn totalSupply(self: @Storage) -> u256 {
self.totalSupply.read()
}
fn balanceOf(self: @Storage, account: ContractAddress) -> u256 {
self.balances.read(account)
}
fn allowance(self: @Storage, owner: ContractAddress, spender: ContractAddress) -> u256 {
self.allowances.read((owner, spender))
}
fn transfer(ref self: Storage, recipient: ContractAddress, amount: u256) {
let sender = get_caller_address();
self.transfer_helper(sender, recipient, amount);
}
fn transferFrom(
ref self: Storage, sender: ContractAddress, recipient: ContractAddress, amount: u256
) {
let caller = get_caller_address();
self.transfer_helper(sender, recipient, amount);
}
fn approve(ref self: Storage, spender: ContractAddress, amount: u256) {
let caller = get_caller_address();
self.approve_helper(caller, spender, amount);
}
fn increase_allowance(ref self: Storage, spender: ContractAddress, added_value: u256) {
let caller = get_caller_address();
self
.approve_helper(
caller, spender, self.allowances.read((caller, spender)) + added_value
);
}
fn decrease_allowance(ref self: Storage, spender: ContractAddress, subtracted_value: u256) {
let caller = get_caller_address();
self
.approve_helper(
caller, spender, self.allowances.read((caller, spender)) - subtracted_value
);
}
}
#[generate_trait]
impl StorageImpl of StorageTrait {
fn transfer_helper(
ref self: Storage, sender: ContractAddress, recipient: ContractAddress, amount: u256
) {
assert(!sender.is_zero(), 'ERC20: transfer from 0');
assert(!recipient.is_zero(), 'ERC20: transfer to 0');
self.balances.write(sender, self.balances.read(sender) - amount);
self.balances.write(recipient, self.balances.read(recipient) + amount);
Transfer(sender, recipient, amount);
}
fn approve_helper(
ref self: Storage, owner: ContractAddress, spender: ContractAddress, amount: u256
) {
assert(!spender.is_zero(), 'ERC20: approve from 0');
self.allowances.write((owner, spender), amount);
Approval(owner, spender, amount);
}
}
}
ERC20 のデプロイ#
まず、再度 cairo ファイルを sierra にコンパイルする必要があります。
$CAIRO_COMPILER_DIR/starknet-compile src/erc20.cairo sierra/erc20.json
次に、宣言します。
starknet declare --contract sierra/erc20.json --account StarknetAstro
今回は出力が異なります。なぜなら、私たちが作成したこの erc20 ファイルはまだ誰もデプロイしていないため、エラーは発生しません(もちろん、コースに従っている学生はすでに宣言されているというエラーに直面するかもしれません)。
Sending the transaction with max_fee: 0.000101 ETH (101461098713976 WEI).
Declare transaction was sent.
Contract class hash: 0x5d144ad5b9af2752da0ef741b565d334a1cb18d02224462d978ebf0a6e8e919
Transaction hash: 0x21da071ce36db1544f482150a8b99902ca1b5db090b66618de724362408ea00
次に、この erc20 コントラクトのインスタンスをデプロイします。erc20 コードのコンストラクタにはパラメータがあるため、対応するパラメータを指定する必要があります:
starknet deploy --inputs 0x417374726F546F6B656E 0x417374726F 0x12 0x3e8 0x0 0x04202409943437c6ebe83697e1ba183976912b667066db7b8796064e59426b0e --class_hash 0x5d144ad5b9af2752da0ef741b565d334a1cb18d02224462d978ebf0a6e8e919 --account StarknetAstro
出力は:
Sending the transaction with max_fee: 0.000791 ETH (790973560660141 WEI).
Invoke transaction for contract deployment was sent.
Contract address: 0x059128b7b3001106211376c12ee86feedd9247033cd1a771e8957efac3b61ca8
Transaction hash: 0x4bcf2d5f9a2e2090755d7a07d2849d4e5cf13fbdd36befd4f73d2030cea392c
大成功です!ここで私たちがデプロイしたコントラクトを見ることができます。
)