Day 5 合约部署与调用
掌握合约的完整生命周期和调用机制。
学习目标
- 掌握合约的完整生命周期
- 理解合约调用机制
- 熟悉 Dry-Run 模拟执行
- 使用 ContractManager 管理合约
1. 合约部署流程
┌─────────────────────────────────────────────────────────────┐
│ 合约部署流程 │
├─────────────────────────────────────────────────────────────┤
│ 1. 编写 Sophia 源码 │
│ │ │
│ ▼ │
│ 2. 编译获取字节码 (cb_...) │
│ │ │
│ ▼ │
│ 3. 生成 init() 的 calldata │
│ │ │
│ ▼ │
│ 4. 构建 ContractCreateTx │
│ │ │
│ ▼ │
│ 5. 签名并广播交易 │
│ │ │
│ ▼ │
│ 6. 获取合约地址 (ct_...) │
└─────────────────────────────────────────────────────────────┘
2. 调用类型
| 方式 | 上链 | 消耗 Gas | 修改状态 | 返回值 |
|---|---|---|---|---|
call | ✅ | ✅ | ✅ | 交易哈希 |
call_static | ❌ | ❌ | ❌ | 直接结果 |
dry_run | ❌ | 模拟 | 模拟 | 模拟结果 |
需要修改状态?
│
├── 是 → 使用 call(需要付费)
│
└── 否 → 只读查询?
│
├── 是 → 使用 call_static(免费)
│
└── 否 → 测试/调试 → 使用 dry_run
3. 实践任务
任务 5.1: 使用 ContractManager 部署合约
from aeternity.node import NodeClient, Config
from aeternity.signing import Account
from aeternity.contract_native import ContractManager
# 准备
client = NodeClient(Config(external_url='https://testnet.aeternity.io'))
# 合约源码
source = '''
@compiler >= 6
contract Counter =
record state = { count : int }
entrypoint init(start : int) = { count = start }
entrypoint get() : int = state.count
stateful entrypoint increment() =
put(state{ count = state.count + 1 })
stateful entrypoint add(n : int) =
put(state{ count = state.count + n })
'''
# 部署流程(需要有余额的账户)
"""
# 1. 创建 ContractManager
manager = ContractManager(
client=client,
account=account,
source=source
)
# 2. 部署合约(调用 init 函数)
tx = manager.deploy("0") # init(0)
# 3. 获取合约地址
print(f"合约地址: {manager.contract_id}")
print(f"部署交易: {tx.hash}")
# 4. 调用合约
# 写入操作(上链)
tx = manager.call("increment")
tx = manager.call("add", "10")
# 读取操作(不上链)
result = manager.call_static("get")
print(f"当前值: {result}")
"""
任务 5.2: 低级 API 部署
from aeternity.node import NodeClient, Config
from aeternity.signing import Account
from aeternity.compiler import CompilerClient
from aeternity import hashing
client = NodeClient(Config(external_url='https://testnet.aeternity.io'))
compiler = CompilerClient()
source = '''
@compiler >= 6
contract SimpleStorage =
record state = { value : int }
entrypoint init(v : int) = { value = v }
entrypoint get() : int = state.value
stateful entrypoint set(v : int) = put(state{ value = v })
'''
print("=== 低级 API 部署流程 ===\n")
# 1. 编译合约
print("1. 编译合约...")
bytecode = compiler.compile(source)
print(f" 字节码: {bytecode[:40]}...")
# 2. 生成 init calldata
print("\n2. 生成 init calldata...")
init_calldata = compiler.encode_calldata(source, "init", ["42"])
print(f" Calldata: {init_calldata[:40]}...")
# 3. 准备部署参数
print("\n3. 部署参数:")
print("""
owner_id: 部署者地址
bytecode: 编译后的字节码
calldata: init 函数的参数
amount: 0(除非 init 是 payable)
gas: 100000(根据合约复杂度调整)
gas_price: 1000000000
""")
# 4. 构建部署交易(伪代码)
print("\n4. 构建部署交易:")
print("""
# 获取 nonce 和 VM 版本
nonce = client.get_next_nonce(account.get_address())
vm_version, abi_version = client.get_vm_abi_versions()
# 构建交易
tx = client.tx_builder.tx_contract_create(
owner_id=account.get_address(),
code=bytecode,
calldata=init_calldata,
amount=0,
gas=100000,
gas_price=1000000000,
vm_version=vm_version,
abi_version=abi_version,
nonce=nonce
)
# 计算合约地址
contract_id = hashing.contract_id(account.get_address(), nonce)
print(f"预计合约地址: {contract_id}")
""")
任务 5.3: 处理合约事件
@compiler >= 6
contract EventDemo =
datatype event =
Transfer(address, address, int)
| Approval(address, address, int)
record state = {
balances : map(address, int),
allowances : map((address, address), int)
}
entrypoint init() = {
balances = {},
allowances = {}
}
stateful entrypoint transfer(to : address, amount : int) =
let from = Call.caller
// ... 转账逻辑 ...
Chain.event(Transfer(from, to, amount))
stateful entrypoint approve(spender : address, amount : int) =
let owner = Call.caller
// ... 授权逻辑 ...
Chain.event(Approval(owner, spender, amount))
# 读取合约事件
# 获取交易信息
tx_info = client.get_transaction_info_by_hash(hash=tx_hash)
# 解析事件
for log in tx_info.call_info.log:
event_hash = log.topics[0] # 事件类型哈希
event_data = log.data # 事件数据
# 使用 ContractManager 解码
events = manager.decode_events(tx_hash)
for event in events:
print(f"{event.name}: {event.args}")
任务 5.4: Dry-Run 模拟
Dry-Run 允许在不上链的情况下模拟交易执行:
- 不消耗真实 Gas
- 不修改链上状态
- 可以预估 Gas 消耗
- 可以获取返回值
# 使用 Dry-Run
result = contract.call_static(
contract_id="ct_xxx...",
function="add",
calldata=compiler.encode_calldata(source, "add", ["10", "20"])
)
# 结果包含
print(f"返回值: {result.return_value}")
print(f"Gas 消耗: {result.gas_used}")
print(f"调用结果: {result.call_type}") # ok 或 revert
4. 常见问题
# 增加 gas 限制
manager = ContractManager(
client=client,
account=account,
source=source,
gas=500000 # 增加 gas
)
- 确认合约已成功部署
- 检查合约地址是否正确
- 确认在正确的网络上
- 检查合约的 require 条件
- 确认调用参数正确
- 使用 dry-run 调试
知识检查点
- 使用 ContractManager 部署合约
- 区分 call 和 call_static
- 使用 Dry-Run 测试
- 读取合约事件
- 处理合约错误
本页目录
预计: 3-4 小时