ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Solidity 抽象合约(Abstract Contracts)完整指南:声明、继承、基类构造函数与实现约束

Solidity 抽象合约(Abstract Contracts)完整指南:声明、继承、基类构造函数与实现约束 Solidity 抽象合约Abstract Contracts完整指南声明、继承、基类构造函数与实现约束【免费下载链接】soliditySolidity, the Smart Contract Programming Language项目地址: https://gitcode.com/GitHub_Trending/so/solidity抽象合约Abstract Contract是 Solidity 面向对象设计中的核心机制它允许开发者先声明接口契约任何继承我的合约都必须实现这个方法再逐步填充实现从而实现定义与实现解耦、模板方法Template method模式以及代码复用。本文以本仓库 Solidity 官方文档 docs/contracts/abstract-contracts.rst 为骨架结合编译器源码libsolidity/analysis/ContractLevelChecker.cpp与语法测试用例系统讲解抽象合约的声明语法、强制抽象条件、继承与覆盖规则、与接口Interface及函数类型的区别以及相关的编译错误信息与规避方法。一、什么是抽象合约何时必须标记abstract在 Solidity 中一个合约必须被标记为abstract只要满足以下任一条件至少有一个函数只有声明、没有实现体即只有函数签名没有{ }函数体没有为所有基类合约的构造函数提供参数无法完成基类构造链。即便不满足上述条件合约仍然可以主动声明为abstract例如你并不打算直接部署该合约而是希望它只作为其他合约的基类被继承。从编译器实现角度看abstract是 ContractDefinition 的一个布尔成员m_abstractAST.h由解析器在读到abstract关键字时置位并在后续分析阶段通过abstract()访问器查询AST.h。同时AST.h 中定义了canBeDeployed()只有当合约既非 abstract 又拥有公开构造函数时才可被部署——这正是抽象合约不能直接实例化在源码层面的直接体现。最简单的抽象合约示例// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.6.0 0.9.0; abstract contract Feline { function utterance() public virtual returns (bytes32); }这里Feline必须声明为 abstract因为函数utterance()只声明了签名没有{ }实现体。如果去掉abstract关键字编译器会直接报错见下文编译器如何检查。二、抽象合约不能直接实例化抽象合约无法被new直接创建即使它实现了所有函数也一样。也就是说abstract关键字本身就构成了一道禁止部署的屏障用于表达这个合约只是设计蓝图不应单独上链的意图。仓库测试 abstract_contract_instantiation.sol 精确演示了这一约束abstract contract AbstractContract { constructor() { } function utterance() public returns (bytes32) { return miaow; } } contract Test { function create() public { AbstractContract ac new AbstractContract(); } } // ---- // TypeError 4614: (208-228): Cannot instantiate an abstract contract.注意示例中的AbstractContract已经完整实现了utterance()并提供了构造函数但因为它显式标记了abstractnew AbstractContract()依然被拒绝编译器报错TypeError 4614: Cannot instantiate an abstract contract.。这与文档中This is also true, if an abstract contract itself does implement all defined functions的表述完全一致。三、继承抽象合约子类必须实现未实现的函数将抽象合约作为基类使用时子类必须通过override实现所有未实现的函数否则子类也必须声明为abstract。文档给出的标准示例// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.6.0 0.9.0; abstract contract Feline { function utterance() public pure virtual returns (bytes32); } contract Cat is Feline { function utterance() public pure override returns (bytes32) { return miaow; } }这里的两个关键点基类声明未实现的函数时使用virtual表明该函数可以在派生链中被覆盖子类实现时使用override明确声明覆盖意图并补上函数体{ return miaow; }。如果子类也不实现呢如果Cat没有覆盖utterance()那么Cat同样必须标记为abstract。仓库测试 abstract_contract_because_of_interface.sol 展示了未实现时编译器的强制检查interface A { function utterance() external returns (bytes32); } contract B is A { } // ---- // TypeError 3656: (69-88): Contract B should be marked as abstract.即使基类只是interface接口中的函数天然全部未实现继承它的B只要没有实现utterance()就必须声明为abstract否则报TypeError 3656: Contract B should be marked as abstract.。传递性抽象性沿继承链向下传递同理抽象合约的子类如果仍未补全实现就必须继续声明abstract抽象性沿继承链传递直到某级子类把所有未实现函数全部覆盖为止。四、基类构造函数参数缺失另一条强制抽象的规则除了函数未实现没有为基类构造函数提供参数也会强制合约成为抽象合约。这是很多 Solidity 开发者容易忽视的规则。编译器在 ContractLevelChecker::checkBaseConstructorArguments 中实现该检查它会遍历线性化的基类列表linearizedBaseContracts逐个核对每个基类构造函数是否拿到了参数若某基类构造函数有参数!baseConstructor-parameters().empty()却没有任何调用点传参且当前合约不是抽象合约则报错TypeError 3415: No arguments passed to the base constructor. Specify the arguments or mark 合约名 as abstract.即两种解决方案二选一补上基类构造函数参数或把当前合约标记为abstract。相关的语法测试用例集中在 test/libsolidity/syntaxTests/constructor/ 目录例如base_constructor_missing_arguments.solbase_constructor_missing_arguments_abstract_modifier_init.solbase_constructor_wrong_arg_count_inheritance_list_abstract.sol这些用例覆盖了继承列表传参与构造函数 modifier 式初始化传参等不同写法下的缺失参数检查逻辑。五、编译器如何检查抽象性源码级原理抽象性检查集中在 ContractLevelChecker::checkAbstractDefinitions其核心逻辑是OverrideProxy 代理收集算法按从基类到派生类的顺序linearizedBaseContracts反转遍历收集合约内所有属于外部接口的状态变量 getter、普通函数、modifier注册为OverrideProxy若派生类提供了实现非unimplemented()则用新实现替换掉基类的未实现代理实现已覆盖语义遍历结束后剩余仍为unimplemented()的代理被记入_contract.annotation().unimplementedDeclarations若合约未声明abstract但unimplementedDeclarations非空则报TypeError 3656should be marked as abstract并把每个缺失实现的位置作为 SecondarySourceLocation 附带到错误信息中ContractLevelChecker.cpp。此外checkAbstractDefinitions还做了三类合法性校验ContractLevelChecker.cpp接口不能声明为 abstract接口本来就是隐式抽象的写abstract interface会报TypeError 9348: Interfaces do not need the abstract keyword, they are abstract implicitly.库Library不能声明为 abstract库的抽象性只能通过函数级错误体现显式写abstract library会报TypeError 9571: Libraries cannot be abstract.普通合约声明abstract则合法solAssert通过。相关语法测试见 test/libsolidity/syntaxTests/abstract/interface.sol 与 test/libsolidity/syntaxTests/abstract/library.sol。另外ContractLevelChecker.cpp 中还可以看到抽象合约不能指定存储布局Storage layout cannot be specified for abstract contracts.因为抽象合约不部署、无实际存储槽位可布置。六、抽象合约 vs 接口Interface文档明确指出抽象合约与 接口Interfaces 相似但接口能声明的内容更受限。二者的本质关系可以总结为能力抽象合约接口已实现函数✅ 可以有实现体❌ 全部函数只能声明构造函数✅ 可以声明❌ 不能声明状态变量✅ 可以声明❌ 不能声明modifier✅ 可以声明❌ 不能声明继承其他合约✅ 可以❌ 只能继承其他接口函数可见性public / external 等均可所有函数必须 external是否可部署❌ 不能直接实例化❌ 不能直接实例化接口本质上是Contract ABI 能表达内容的子集因此接口与 ABI 之间可以无损互转见 docs/contracts/interfaces.rst。而抽象合约能力更强是带部分实现的接口更接近传统面向对象语言中的抽象基类。设计取舍建议只需要声明一组对外调用约定、且不涉及状态与实现的纯协议时优先使用interface需要在基类中沉淀共享状态、公共实现逻辑、modifier只留个别钩子函数给子类实现时使用abstract contract这正是模板方法模式的落地形态。七、函数声明 vs 函数类型两种易混淆的语法文档特别提醒没有实现体的函数声明与函数类型Function Type变量语法非常相似但含义完全不同切勿混淆。函数声明未实现即抽象函数的声明function foo(address) external returns (address);函数类型变量的声明注意foo前面的位置是类型、后面是变量名function(address) external returns (address) foo;第一段代码是函数foo的声明它让包含它的合约必须成为抽象合约第二段代码声明了一个名为foo的变量其类型是接收一个address参数、返回address的外部函数。函数类型的完整语法与用法见 docs/types/value-types.rst 中的 Function Types 小节。八、关键约束不能把已实现的虚函数改回未实现文档末尾有一条重要约束note抽象合约不能用一个未实现的函数去覆盖override一个已经实现的虚函数。也就是说沿继承链只能从声明走向实现不能反向从实现退回声明。一旦基类某个virtual函数已有函数体派生类就必须提供新的函数体或继续使用基类实现而不能只写一个无函数体的签名来覆盖它。这条规则保证了派生链上每个函数始终有可解析的最终实现避免部署时出现函数体悬空。九、抽象合约的设计价值解耦、模板方法与自文档化文档指出抽象合约将合约的定义definition与合约的实现implementation解耦带来三方面收益更好的可扩展性extensibility在不改动基类的前提下通过继承与覆盖扩展行为更好的自文档化self-documentation抽象合约本身就是一份子类必须实现什么的契约清单读代码即知设计意图支撑模板方法Template method模式基类用已实现的函数编排算法骨架把可变步骤留给抽象函数由子类填充从而消除重复代码。其价值与在接口中定义方法一致——它让抽象合约的设计者向所有子类声明我的任何子类都必须实现这个方法。十、快速排错清单编译错误含义解决方法TypeError 3656: Contract X should be marked as abstract.存在未实现函数/modifier补全所有未实现函数或给合约加abstractTypeError 4614: Cannot instantiate an abstract contract.尝试new抽象合约不要直接实例化改为继承它并实现全部函数后再部署子类TypeError 3415: No arguments passed to the base constructor. Specify the arguments or mark X as abstract.基类构造函数缺参在继承列表或 modifier 初始化处传参或标记为 abstractTypeError 9348: Interfaces do not need the abstract keyword...接口误加abstract移除关键字接口本身即隐式抽象TypeError 9571: Libraries cannot be abstract.库误加abstract移除关键字通过函数级实现保证完整性以上错误码与消息均可在 test/libsolidity/syntaxTests/abstract/ 与 test/libsolidity/syntaxTests/constructor/ 目录下的语法测试中看到对应的预期输出是调试抽象合约相关编译错误的第一手参考资料。总结抽象合约是 Solidity 在接口的纯声明与普通合约的完整实现之间提供的中间地带它允许你携带状态、构造函数、modifier 和部分实现同时把关键钩子留给子类。理解它的三条核心规则——存在未实现函数时必须抽象、未提供基类构造参数时必须抽象、抽象合约禁止直接实例化——以及它与接口、函数类型之间的边界是写出可扩展、可维护的合约继承体系的基础。本文全部示例与报错信息均可在仓库的 docs/contracts/abstract-contracts.rst、libsolidity/analysis/ContractLevelChecker.cpp 及 test/libsolidity/syntaxTests/abstract/ 中找到对应依据。【免费下载链接】soliditySolidity, the Smart Contract Programming Language项目地址: https://gitcode.com/GitHub_Trending/so/solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表