script (⏱,💰)

script (⏱,💰)

NG#8 - 合约账户

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 交易。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。