eosio c++合约必须使用eosio.cdt编译,依赖其专用工具链和头文件;apply为唯一c风格入口,需按签名实现并手动路由;表操作须用multi_index且主键限uint64_t或含primary_key()的结构体;权限检查需区分code与receiver场景,避免授权绕过。

EOSIO C++ 合约必须用 eosio.cdt 编译,不能用普通 g++ 或 clang
直接用系统自带编译器会报一堆符号未定义错误,比如 undefined reference to 'eosio::print' 或 undefined reference to 'eosio::require_auth'。因为 EOSIO 的 ABI 生成、内存模型、系统调用入口(如 apply)都依赖 eosio.cdt 提供的专用工具链和头文件。
实操建议:
- 从 官方 releases 页面 下载匹配你链版本的
eosio.cdt(例如 EOSIO 2.1 链建议用 cdt v1.8.x,2.2+ 推荐 v1.9+) - 安装后确保
clang++指向的是eosio-cpp包装脚本(运行eosio-cpp --version确认输出含 "EOSIO CDT") - 不要手动 include
<eosiolib></eosiolib>—— 新版 CDT 已废弃该路径,统一用<eosio></eosio>
apply 函数是唯一入口,必须按签名 extern "C" void apply(uint64_t receiver, uint64_t code, uint64_t action) 实现
EOSIO 不调用 main(),所有动作触发都走这个 C 风格函数。它不是你写逻辑的地方,而是路由分发点:你需要手动判断 code == receiver(是否本合约发起),再用 if (action == name("transfer").value) 分支处理具体动作。
常见错误现象:
- 忘记
extern "C"导致链接失败(undefined symbol apply) - 把业务逻辑全塞进
apply里,导致难以测试和维护 - 没检查
code == receiver就直接处理,结果响应了其他合约发来的 action(比如被恶意伪造的transfer)
推荐结构:
extern "C" void apply(uint64_t receiver, uint64_t code, uint64_t action) {
if (code == receiver) {
if (action == name("transfer").value) {
execute_transfer();
} else if (action == name("issue").value) {
execute_issue();
}
}
}
表操作必须用 multi_index,且主键类型只能是 uint64_t 或支持 primary_key() 的结构体
EOSIO 不支持任意字段做主键索引,也不允许用 std::map 或 vector 存持久化数据。所有链上状态必须通过 multi_index 定义,并显式指定主键(id 字段或自定义 primary_key() 方法)。
关键约束:
- 主键必须是
uint64_t,或结构体中提供uint64_t primary_key() const成员函数(返回值仍得是uint64_t) - 不能用
string、name、asset直接当主键 —— 它们不满足uint64_t要求 - 二级索引需用
indexed_by显式声明,且索引字段类型必须支持比较(如uint64_t,time_point_sec)
示例(合法主键):
struct [[eosio::table]] account {
uint64_t id;
name owner;
asset balance;
uint64_t primary_key() const { return id; }
};
权限检查不能只靠 require_auth,要区分 code 和 receiver 场景
require_auth(who) 只验证交易中是否有 who 的签名,但它不保证该签名来自动作发起者(code)还是合约自己(receiver)。典型陷阱是:在 transfer 动作里对 from 调用 require_auth(from),但没确认这个 from 是否真的发起了该动作 —— 攻击者可构造一笔带多余授权的交易绕过。
安全做法:
- 对动作发起方(即
code)校验:用require_auth(code)(适用于合约升级、配置类动作) - 对用户账户(如转账的
from)校验:先确认该账户在get_action()中作为account出现在当前 action 的authorization列表里,再调用require_auth(from) - 避免在
apply外部函数中漏掉权限检查 —— 所有修改状态的函数都应显式校验
容易被忽略的一点:ABI 文件里定义的 action 名称必须和代码中 name("xxx") 完全一致(包括大小写),否则 require_auth 可能作用于错误上下文。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











