契約アカウントには 3 つのサブタスクがあり、これらはすべて「bad account」シリーズを構成しています。この記事はシリーズの最初のタスク「Stealing Souls」です。
タスクをダウンロードすると、2 つのアカウント契約があります。それぞれ tombkeeper_1.cairo と tombkeeper_2.cairo で、デプロイ時には自動的に契約に 100 個の $SOUL が mint され、トランザクションごとに 0.1 個の $SOUL が差し引かれます。タスクの要件は、$SOUL をすべて盗むことです。
契約 1#
validate_calls にはコメントがあり、相互作用する契約アドレスがブラックリスト(=Soul ERC20 契約アドレス)でないことを検証します。つまり、Transfer Token のトランザクションを直接送信することはできません。
fn validate_calls(mut calls: Array<Call>, blacklisted: ContractAddress) {
match calls.pop_front() {
Option::Some(call) => {
// Trying to steal some soul? Nice try...
assert(call.to != blacklisted, 'CANNOT_CALL_SOUL_TOKEN');
},
Option::None(_) => { return (); }
}
validate_calls(calls, blacklisted)
}
ただし、この契約の__execute__
には caller の検証が欠落しており、__validate__
をスキップして__execute__
を呼び出すことができます。
snforge のテストでは、手数料を差し引いた soul トークンの一部を盗むための call トランザクションを構築し、ローカルテストに合格した後、テストの calls をシリアライズして、sncast またはブラウザを使用してcalls: Array<felt252>
を__execute__
に送信してタスクを完了します。
契約 2#
契約 2 ではバグ修正が行われています:
fn __exeute__
にassert(get_caller_address().is_zero(), 'INVALID_CALLER');
が追加され、他の契約ウォレットから直接関数を呼び出すことはできなくなりました。- to が $SOUL の契約の場合、非常に大きな IMPOSSIBLE_SOUL_FEE を gas として必要とします。u256 の型ではオーバーフローすることはできません。
if call.to == blacklisted {
// Trying to steal some soul? Nice try...
total_fee + IMPOSSIBLE_SOUL_FEE
} else {
total_fee + SOUL_FEE
}
まず、__validate__
に 1000 個の call の calldata を渡す方法を試しました。total_fee を 100 にするために、適当な call を作成して execute を 1 回呼び出してトークンを転送します。
total_fee を変更した後、最初の execute で契約が呼び出されないようにするために、最初に契約が呼び出される前に caller の検証が行われます。これにより、starkli やブラウザを使用して直接呼び出す方法が防止されます。
アドバイザーに相談した後、SDK を使用して Tombkeeper をアカウントとして他の契約を呼び出すために、ローカルプライベートキーで署名することができるとのヒントを得ました。これには SDK が必要です。
私は入門 CTFの中の sand_devils 契約を再デプロイし、count を 1000 に設定しました。1000 から任意の数字を引くことができます。
SDK にはさまざまな言語のバージョンがありますが、私は starknet.js を使用しました。重要なステップは次のとおりです:
getClassAt
を使用して abi を取得し、sand_devil の Contract インスタンスを作成します。- Tombkeeper2 アカウントアドレスと契約のデプロイ時に使用したプライベートキーを使用して Account インスタンスを作成します。
await devilContract.invoke("slay", [1],{ parseRequest: false}
を使用してトランザクションを送信します。
結果として、手数料は 0.1 SOUL しか差し引かれませんでした。以前の 1000 個の__validate__
は手数料を 100 SOUL に増やしませんでした。おそらく、__validate__
だけでは状態の変更が発生しないため、__validate__
と__execute__
を同時に実行する必要があります。
それでは、starknet.js で 1000 個の calls のトランザクションを直接送信してみましょう。__validate__
と__execute__
が同時に実行されるはずですし、アカウント契約はトランザクションのパッキングをサポートしています。
ハッシュをブラウザで確認すると、1000 回の slay と 1 回の transfer が 1 つのトランザクションに内包され、TompAccout の SOUL がすべて消費されました。
まとめ#
アカウントの抽象化は StarkNet の非常に重要な構成要素であり、2 つのタスクはそれぞれ契約側と SDK 側からカスタム契約を操作することで、ユーザーにアカウントの抽象化についての理解を深める機会を提供します。また、SDK の使用方法を初めて学び、バンドルトランザクションを送信する方法を理解しました。