NG#8 - 合约账户

关于合约账户有 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 的交易。

Copy
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。

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