xChar

NG8 start

关于合约账户有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 end

账户抽象是 StarkNet 中很重要的组成部分,两个任务分别从合约端和SDK端去操作自定义合约,让用户加深了抽象账户的理解。并且初步接触了SDK的使用,了解该如何使用SDK连接合约发送bundle交易。

Loading comments...