script (⏱,💰)

script (⏱,💰)

NG#8 - 合約帳戶

NG8 開始

關於合約帳戶有 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,重要步驟:

  1. 通過getClassAt拿到 abi,創建 sand_devil 的 Contract 實例。
  2. 使用 Tombkeeper2 帳戶地址和部署該合約的私鑰創建 Account 實例。
  3. 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 全部耗完了。

總結#

ng8 結束

帳戶抽象是 StarkNet 中很重要的組成部分,兩個任務分別從合約端和 SDK 端去操作自定義合約,讓用戶加深了抽象帳戶的理解。並且初步接觸了 SDK 的使用,了解該如何使用 SDK 連接合約發送 bundle 交易。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。