關於合約帳戶有 3 個子任務,共同組成了 bad account 系列。本文是系列的第一個任務 Stealing Souls。
下載任務後有 2 個帳戶合約,分別是 tombkeeper_1.cairo 和 tombkeeper_2.cairo,部署時會自動 mint 100 個 $SOUL 到合約裡,每次執行交易會扣 0.1 個 $SOUL,任務要求把 $SOUL 全部偷走。
合約 1#
在 validate_calls 中有註釋,會驗證交互的合約地址不是 blacklisted(=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 的測試中,構造一筆 call 轉移除去手續費的 soul token,本地測試通過後,把測試的 calls 進行 serialized,用 sncast 或者瀏覽器發送calls: Array<felt252>
給__execute__
完成任務。
合約 2#
合約 2 做了 bug 修復:
fn __exeute__
中添加了assert(get_caller_address().is_zero(), 'INVALID_CALLER');
,沒法通過其他合約錢包去直接調用函數了。- 如果 to 是 $SOUL 的合約,需要一個非常大數值的 IMPOSSIBLE_SOUL_FEE 作為 gas。類型是 u256 也沒法 overflow。
if call.to == blacklisted {
// Trying to steal some soul? Nice try...
total_fee + IMPOSSIBLE_SOUL_FEE
} else {
total_fee + SOUL_FEE
}
首先我嘗試了方法傳一個 1000 個 call 的 calldata 給__validate__
,打算把 total_fee 刷到 100 後,再隨便建一個 call 調用一次 execute 把 token 轉走。
修改完 total_fee 後,發現 execute 裡一開始就設置了不能由合約調用,原因是防止被其他合約攻擊。這就堵死了用 starkli 和瀏覽器直接調用的方法。
在詢問導師後,提示可以在本地私鑰簽名,把 Tombkeeper 當作 accout 去調用其他合約來完成任務,這需要用到 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 個 call 的__validate__
沒有把手續費增加到 100 SOUL。看樣子底層有判斷如果沒有調用__execute__
,單獨的__validate__
是不會引起狀態改變的。
那嘗試直接在 starknetjs 發送一筆 1000 個 calls 的交易,應該會同時完成__validate__
和__execute__
,帳戶合約是支持交易打包操作的。
想要發送打包交易,在 starknet.js 中contract.populate
可以把地址、變數和參數轉換成內置的 Call 類型,account.execute
支持發送Call[]
。儘管有 1000 筆交易,也能很快確認。
瀏覽器查看 hash,成功在一筆交易中內置了 1000 筆 slay 和一筆 transfer,把 TompAccout 中的 SOUL 全部耗完了。
總結#
帳戶抽象是 StarkNet 中很重要的組成部分,兩個任務分別從合約端和 SDK 端去操作自定義合約,讓用戶加深了抽象帳戶的理解。並且初步接觸了 SDK 的使用,了解該如何使用 SDK 連接合約發送 bundle 交易。