在阅读 OlympusV3 的合约时,架构的设计给我留下很深的印象,架构清晰,在智能合约领域实现了高内聚低耦合的设计思想,从而可以专注的深入独立的模块阅读,降低了心智负担。
OlympusV3 的架构图如下所示:
后来了解到这种框架也并非 Olympus 独创,在 Olympus 介绍合约架构的文章中提到了Default 框架
智能合约架构的复杂性#
在介绍 Default 框架之前,作者首先对比了传统的协议如 AAVE MakerDAO 的架构图
光看架构图,开发者是很难厘清其中的调用链路的,作者将这种设计解释为以过程为中心,按照逻辑链的思路来组织
Today, most projects take a process-centric approach towards protocol development: each smart contract is developed to manage a process along a chain of logic that is triggered when external interactions occur.
那么随着业务的升级和更替,逻辑链路的复杂度会指数级增长,原因在于整个协议呈现网状架构,或许每两个点之间都有逻辑链,那么每增添一个点,带来的复杂度提升是难以估量和覆盖的。
正如软件工程领域的设计模式演进过程一样,智能合约的工程化架构也在不断改进,Default Framework 正是着手解决这个问题
什么是 The Default Framework#
光从名字看不出来设计的思路,不像Diamond 框架能够让人产生联想。The Default Framework 也没有做特别的创新,而是借鉴了传统领域开发的设计思想,对整个合约进行了分层
- Modules:面向内部,模块可以理解为微服务,每个模块管理一部分数据,彼此之间不存在依赖关系,高度独立,对于模块的访问都到权限管理约束
- Policy:面向外部调用,在内部负责组织不同的模块以实现业务逻辑
核心的管理逻辑都收口在Kernal.sol文件中,负责管控全局的 Policy 和 Modules
Modules#
每个 Modules 包括独一无二的 KEYCODE 及 VERSION,在定义 Modules 时需要实现此方法,在 Kernal 中会维护所有的 keycode 及其与 Module 的映射关系
在 Kernal 中包括以下方法
- _installModule:安装 module
- _upgradeModule:升级 module
示例:
/// @inheritdoc Module
function KEYCODE() public pure override returns (Keycode) {
return toKeycode("MINTR");
}
/// @inheritdoc Module
function VERSION() external pure override returns (uint8 major, uint8 minor) {
major = 1;
minor = 0;
}
Policy#
Policy 可以理解为传统软件开发的 service 层,用来组织一系列 Modules,包括以下方法:
- configureDependencies:返回支持哪些 Modules
- requestPermissions:返回支持的 Modules 的具体函数 selector
在 kernal 中包括以下方法:
- _activatePolicy
- _deactivatePolicy
示例:
/// @inheritdoc Policy
function configureDependencies() external override returns (Keycode[] memory dependencies) {
dependencies = new Keycode[](3);
dependencies[0] = toKeycode("MINTR");
dependencies[1] = toKeycode("TRSRY");
dependencies[2] = toKeycode("ROLES");
MINTR = MINTRv1(getModuleAddress(dependencies[0]));
TRSRY = TRSRYv1(getModuleAddress(dependencies[1]));
ROLES = ROLESv1(getModuleAddress(dependencies[2]));
(uint8 TRSRY_MAJOR, ) = TRSRY.VERSION();
(uint8 MINTR_MAJOR, ) = MINTR.VERSION();
(uint8 ROLES_MAJOR, ) = ROLES.VERSION();
// Ensure Modules are using the expected major version.
// Modules should be sorted in alphabetical order.
bytes memory expected = abi.encode([1, 1, 1]);
if (MINTR_MAJOR != 1 || ROLES_MAJOR != 1 || TRSRY_MAJOR != 1)
revert Policy_WrongModuleVersion(expected);
}
/// @inheritdoc Policy
function requestPermissions() external view override returns (Permissions[] memory requests) {
Keycode MINTR_KEYCODE = MINTR.KEYCODE();
requests = new Permissions[](4);
requests[0] = Permissions(MINTR_KEYCODE, MINTR.mintOhm.selector);
requests[1] = Permissions(MINTR_KEYCODE, MINTR.burnOhm.selector);
requests[2] = Permissions(MINTR_KEYCODE, MINTR.increaseMintApproval.selector);
requests[3] = Permissions(MINTR_KEYCODE, MINTR.decreaseMintApproval.selector);
}