合约账户基础

合约账户可以存储以太币并且拥有以太币。与外部账户不一样,它自身不能发起交易。向合约账户发送交易,就等同于调用合约里的函数。然而,在被调用的时候,它能够向外发送交易。例如在以太坊的应用场景里,开发者构建的一些去中心化应用,会利用合约账户来执行特定的逻辑。

在以太坊网络当中,合约账户具有独特的地位。它是智能合约得以运行的载体。许多基于以太坊进行开发的项目,像借贷平台、去中心化交易所等,都要依靠合约账户来对资金进行处理以及执行代码,从而使整个应用能够按照预先设定好的规则运转起来。

以太坊交易实质

以太坊中的交易是由签名消息数据包构成的,它从一个账户发送到另一个账户。无论是进行转账操作,还是创建智能合约以及调用智能合约,都必须通过交易来实现。例如,用户向他人转账以太币,在以太坊网络中这就体现为一个交易行为。

交易在区块链世界中具有基础性和重要性。日常生活中,我们借助支付宝、微信进行转账,而在以太坊中,正在发生着一个个交易。每一笔交易都有着详细的记录,这些记录被保存在区块链上,具备可溯源的特性且不可被篡改。

交易类型及手续费

创建合约交易就是将合约部署到链上,从而形成一个合约账户;而调用合约交易则是对合约账户里的函数进行调用。交易手续费指的是交易发送者愿意为交易支付的、单位 gas 所对应的以太币的价值,并且是以 wei 为单位的。在 2017 年以太坊大火的时候,交易手续费的波动是比较大的。

交易手续费的高低对很多操作会产生影响。手续费高时,用户在进行小额转账或者合约调用时会更加小心谨慎,因为成本提升了。对于开发者而言,设置恰当的手续费是需要加以考虑的问题,毕竟过高的手续费很可能会对用户体验造成影响。

合约编译与部署

if (msg.value) { revert(); } // 如果不是 payable 的话会有这句
if (msg.data.length == 0) { fallback(); } // 如果没有 fallback 就 revert
if (msg.data[0:4] == selector1) {
    function1();
} else if (msg.data[0:4] == selector2) {
    function2();
} else {
    fallback(); // 如果有 fallback
    // revert(); // 如果没有 fallback
}

合约编写完成之后,需要经过编译器的处理,从而转变为 EVM 能够执行的字节码。在创建合约交易的时候,该交易的 to 字段是空白的,而 data 字段则是经过编译后的合约字节码。当执行这个交易时,EVM 就会去执行 data 字段里的字节码,其中有些执行操作之后会有回显出现。

这个过程犹如开发一款软件,当代码编写完毕后,需要将其编译成计算机能够识别的二进制文件。在以太坊当中,只有经过编译的合约才能够在 EVM 环境中运行,并且可以部署到链上。不同的编译器或许会具备不同的特性,所以开发者需要挑选合适的编译器。

合约存储机制

以太坊为合约提供了存储空间。它类似一个大小为\(2^{256}\)的数组。这个数组的每个元素是 32 字节的插槽。这些存储是公开的。即便在合约里被规定为私有变量。在存储里也不会被隐藏。这个设计保障了区块链的透明性。

开发者需要了解存储机制。例如在设计合约时,要对存储变量的位置进行合理规划,以避免资源的不必要浪费。因为在以太坊网络里,存储操作较为昂贵,所以开发者得想办法优化代码,降低存储资源的使用。

与以太坊网络交互

在以太坊协议的 geth 实现里,能够通过 rpc 的方式来与以太坊网络进行交互。对于 CTF 题目而言,一般会存在私链以及 rpc 接口,能够用 geth 进行连接并且执行命令。不过因为防火墙的限制,使用 web3.js 或者 web3.py 会更加便捷,并且在发送交易的时候需要先手动进行签署。

在互联网中存在不同的协议和工具用于连接不同的服务器。在以太坊的开发与使用过程里,我们拥有各种工具和方式来与网络进行通信。对于普通的开发者以及 CTF 参与者而言,选取合适的交互方式能够提升效率。那么,当你在利用以太坊进行开发或者参与相关活动时,都遇到过哪些与交互相关的问题?

from web3 import Web3
# from rich import print

w3 = Web3(Web3.HTTPProvider('...'))

hacker = '...'
target = '...'
privateKey = '...'

def get_txn(src, dst, data, value=0, gas=0x200000):
    return {
        "chainId": w3.eth.chainId,
        "from": src,
        "to": dst,
        "gasPrice": w3.toWei(1.1, 'gwei'),
        "gas": gas,
        "value": w3.toWei(value, 'ether'),
        "nonce": w3.eth.getTransactionCount(src),
        "data": data
    }

def transact(src, dst, data, value=0, gas=0x200000):
    data = get_txn(src, dst, data, value, gas)
    transaction = w3.eth.account.signTransaction(data, privateKey).rawTransaction
    txn_hash = w3.eth.sendRawTransaction(transaction).hex()
    txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)
    return txn_receipt

print("[+] Deploying exploit contract...")
txn_receipt = transact(hacker, None, bytes.fromhex("..."))
print(txn_receipt)
print("[*] Exploit contract deployed at", txn_receipt['contractAddress'])
contractAddress = txn_receipt['contractAddress']