Euler 模块代理模式(Module Proxy Mode)深度解析:可独立升级的模块化可升级合约架构

发布时间:2026/10/13 9:20:28

Euler 模块代理模式(Module Proxy Mode)深度解析:可独立升级的模块化可升级合约架构 示例工程区块链【免费下载链接】Dapp-LearningDapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.项目地址https://gitcode.com/gh_mirrors/da/Dapp-Learning点击查看免费下载模块代理模式是 Euler 借贷协议在合约工程实现上的一大亮点它在传统单代理Proxy可升级架构的基础上将协议拆分为主入口代理 多个功能模块使每个模块都能独立安装与升级而协议核心状态始终集中存储在主入口合约上。本文以仓库文档 defi/Euler/ModuleProxyMode.md 为主体骨架结合仓库内 Proxy.md、BaseLogic.md、contracts/README.md 等源码级分析文档完整梳理模块代理的整体架构、部署/安装/升级流程、调用链路与底层 assembly 代码逻辑帮助读者掌握这一可复用的模块化可升级架构设计。背景从传统 Proxy 到模块化 Proxy传统的代理Proxy模式只有一个入口代理合约与一个逻辑Implementation合约所有调用先经过 Proxy 的 fallback再由delegatecall转发到唯一的逻辑合约。这种模式最大的问题是不可模块化——无论协议中哪一个功能需要升级都必须整体替换逻辑合约升级范围大、风险面广也难以对单个功能进行版本管理。Euler 对此进行了改进不再把协议当作一个整体而是拆分成多个可独立管理的模块每个模块都可以单独升级。根据仓库文档 contracts/README.mdEuler 一共有 9 个模块模块职责DToken债务凭证EToken抵押凭证Exec执行合约可批量执行操作Governance治理模块Installer模块安装器涉及模块的设置和权限Liquidation清算模块Markets资产市场存储资产在借贷市场中的相关参数如BorrowFactor、CollateralFactorRiskManager风险管理模块Swap交易模块用户 EToken 之间的交易不是 underlying相较于常规 Proxy 模式这种模块化升级模式具备以下特点同样出自 contracts/README.md可以将合约拆分成各个模块单独管理版本例如仅升级其中一个模块而无需升级所有逻辑合约协议所有 Storage 状态变量都集中在入口合约Euler.sol上而非分布于不同模块的 Proxy 合约上状态管理更加方便部分日志可以由模块 Proxy 发出而不是统一由入口合约发出详见后文emitViaProxy_Transfer与emitViaProxy_Approval每个模块都有moduleId和moduleGitCommits作为特征moduleId是每种模块的固定编号moduleGitCommits是版本标记便于比对。整体架构MainProxy Installer 功能模块模块代理的整体架构由三层角色组成主入口代理合约MainProxy即Euler.sol协议所有状态的存储地也是模块调度的中枢负责把调用路由到对应模块的逻辑合约Installer 模块模块安装器负责新模块的安装与权限管理功能模块每个功能模块都由两个合约构成——Implementation 合约执行逻辑与ModuleProxy 合约代理。也就是说每个模块都有一个 Implementation 合约存放逻辑代码和一个 ModuleProxy 合约作为用户入口与状态桥接。而所有这些模块 Proxy 都由 MainProxy 通过模板创建并登记ModuleProxy 与 Implementation 之间通过映射mapping绑定。模块代理模式的调用过程非常绕见后文调用流程但这样做有一个很大的优势由于最终是由 MainProxy 使用delegatecall调用具体逻辑Euler 合约的主要状态都存储在主入口代理合约 MainProxy 上因此模块的安装/升级并不会影响协议的状态。合约部署流程原文档给出了主入口合约的部署流程共三步部署 Installer 模块的 Implementation执行逻辑合约传入步骤 1 的地址作为入参部署 MainProxy 主入口合约MainProxy 构造函数通过 ModuleProxy 模板创建一个 Installer 模块的 Proxy 合约并将两者地址绑定mapping。仓库 Proxy.md 中给出了Euler.sol构造函数的对应源码片段可以印证上述流程// src/Euler.sol contract Euler is Base { constructor(address admin, address installerModule) { ... moduleLookup[MODULEID__INSTALLER] installerModule; address installerProxy _createProxy(MODULEID__INSTALLER); trustedSenders[installerProxy].moduleImpl installerModule; } ... }这里的关键动作有两个moduleLookup[MODULEID__INSTALLER] installerModule;把 Installer 模块的逻辑合约地址注册到模块查找表moduleLookup中建立模块编号 → 逻辑实现的映射address installerProxy _createProxy(MODULEID__INSTALLER);调用_createProxy创建 Installer 模块的 ModuleProxy 代理合约trustedSenders[installerProxy].moduleImpl installerModule;在trustedSenders中把代理地址与逻辑实现绑定。其中_createProxy是模块代理创建的核心函数定义于 BaseLogic.md其源码为function _createProxy(uint proxyModuleId) internal returns (address) { require(proxyModuleId ! 0, e/create-proxy/invalid-module); require(proxyModuleId MAX_EXTERNAL_MODULEID, e/create-proxy/internal-module); // If weve already created a proxy for a single-proxy module, just return it: if (proxyLookup[proxyModuleId] ! address(0)) return proxyLookup[proxyModuleId]; // Otherwise create a proxy: address proxyAddr address(new Proxy()); if (proxyModuleId MAX_EXTERNAL_SINGLE_PROXY_MODULEID) proxyLookup[proxyModuleId] proxyAddr; trustedSenders[proxyAddr] TrustedSenderInfo({ moduleId: uint32(proxyModuleId), moduleImpl: address(0) }); emit ProxyCreated(proxyAddr, proxyModuleId); return proxyAddr; }从源码结构可以提炼出几个实现细节模块编号校验proxyModuleId ! 0防止非法编号proxyModuleId MAX_EXTERNAL_MODULEID则区分外部模块与内部模块内部模块不可直接创建外部代理单代理模块复用proxyLookup缓存了单代理模块已创建的代理地址若已创建则直接返回避免重复创建MAX_EXTERNAL_SINGLE_PROXY_MODULEID界定哪些模块是单代理模块代理创建方式address(new Proxy())直接通过new关键字部署一个全新的Proxy模板实例信任发送方登记trustedSenders[proxyAddr] TrustedSenderInfo({...})记录该代理归属的模块编号这是后续鉴权判断调用来源是否可信的基础创建事件发出ProxyCreated(proxyAddr, proxyModuleId)事件便于链上索引与监控。在后续激活新资产市场时这一机制同样被复用。例如 Markets.md 中doActivateMarket会为每种资产创建 EToken/DToken 两个模块代理address childEToken params.config.eTokenAddress _createProxy(MODULEID__ETOKEN); address childDToken _createProxy(MODULEID__DTOKEN);功能模块安装/升级流程当协议需要新增或升级某个功能模块时走的是 Installer 模块的安装流程原文档给出四步部署新模块的 Implementation 合约可以一次部署多个模块将新模块的地址数组传入 Installer Proxy调用InstallModules()函数函数内部遍历这些模块并为每个模块创建对应的 ModuleProxy 合约将每个新模块的 Implementation 和 ModuleProxy 绑定mapping完成新模块安装。结合_createProxy的源码可以看出第 3、4 步的具体语义InstallModules()内部对地址数组逐个调用_createProxy(moduleId)生成 ModuleProxy随后通过trustedSenders[proxyAddr].moduleImpl implementation完成逻辑合约与代理的绑定。这样的设计带来的直接好处是升级某个模块时只需部署新的 Implementation并通过 Installer 更新绑定关系即可其他模块的代理与逻辑完全不受影响由于协议状态都保存在 MainProxy 上模块替换过程也不会触碰任何用户资金状态。用户调用模块功能接口流程用户在 Euler 中调用某个模块的功能如存款、借款时实际走的是用户 → 模块 Proxy → MainProxy.dispatch → 模块 Implementation的多级转发链路原文档概括为三步向模块的 ModuleProxy 发起调用注意不是直接调用 MainProxyModuleProxy 会使用call操作码来调用 MainProxy 的dispatch函数dispatch函数会使用delegatecall操作码来调用模块的 Implementation 执行逻辑合约。仓库 hack/EulerAttack.md 中记录的eToken.deposit业务流程图正是这条链路的直观体现sender - proxy(eToken): call deposit proxy(eToken) - Euler: call dispatch Euler - Impl(eToken): delegatecall deposit sender -- Euler: Dai transfer amountA Impl(eToken) -- sender: mint eDai amountB ... Euler - proxy(eToken): call emitEvent这条链路同样适用于withdraw、mint、borrow、repay、transferFrom等所有模块操作区别仅在于入口是哪个模块的 Proxy例如借款走proxy(dToken)。链路虽长却是有意为之的架构权衡用户面对的是模块专属的代理地址如 eToken Proxy、dToken Proxy语义清晰中间层MainProxy 承担路由与状态载体的角色最内层模块 Implementation 只负责逻辑执行其代码运行在 MainProxy 的存储上下文之上。核心代码逻辑深度解析ModuleProxy 的 fallbackcalldata 重组与转发ModuleProxy 合约本身没有业务函数所有调用都会进入fallback这也是常见 Proxy 的原理。关键在于其 fallback 对 calldata 做了重组原文档给出了重组后的数据布局dispatch selector calldata caller address然后使用call操作码调用 MainProxy。仓库 Proxy.md 中给出了完整的 fallback 源码其中非创建者分支如下} else { assembly { mstore(0, 0xe9c4a3ac00000000000000000000000000000000000000000000000000000000) // dispatch() selector calldatacopy(4, 0, calldatasize()) mstore(add(4, calldatasize()), shl(96, caller())) let result : call(gas(), creator_, 0, 0, add(24, calldatasize()), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } }逐行解读这段 assemblymstore(0, 0xe9c4a3ac...)在内存起始位置写入dispatch()的函数选择器selector 为0xe9c4a3accalldatacopy(4, 0, calldatasize())把原始 calldata 拷贝到内存偏移 4 处紧跟在 dispatch selector 之后mstore(add(4, calldatasize()), shl(96, caller()))在原始 calldata 之后追加 20 字节的调用者地址caller()通过左移 96 位对齐到高 20 字节call(gas(), creator_, 0, 0, add(24, calldatasize()), 0, 0)以重组后的 calldata长度24 calldatasize调用creator_即 MainProxy。这里有两个关键点creator_就是 MainProxy。由于所有模块的 Proxy 都是由 MainProxy 创建而Proxy的构造函数为constructor() { creator msg.sender; }见 Proxy.md所以创建者即 MainProxy 地址调用链中附加了caller()原始调用者地址。这是为了在下一层dispatch中恢复真实的用户地址——因为经 ModuleProxy 转发后MainProxy 看到的msg.sender是 ModuleProxy 而非用户必须显式地把用户地址附加在 calldata 尾部传递下去。MainProxy 的 dispatch长度校验与路由执行接下来进入 MainProxy 的dispatch函数。它首先会检查 calldata 长度因为从 ModuleProxy 发送过来的调用都经过重组合法的调用长度最小为 28 字节其构成如下原文档给出4 dispatch selector 4 some function selector (payload) 20 caller address即4 字节的dispatch函数选择器、4 字节的模块函数选择器、可变的 payload、以及 20 字节的调用者地址最小长度4 4 20 28字节。长度校验可以过滤掉那些未按模块代理协议组装的不合法调用。通过长度校验后dispatch会使用delegatecall操作码调用模块的执行逻辑合约把控制权交给对应模块的 Implementation。这里与 ModuleProxy 形成鲜明对比注意ModuleProxy 中使用call调用而 MainProxy 中使用delegatecall调用所以函数运行逻辑在模块的 Implementation 中而所使用的状态在 MainProxy 中。这个一段call、一段delegatecall的组合是模块代理模式的精髓ModuleProxy → MainProxy 用call创建独立的调用上下文MainProxy 的dispatch代码在自己的存储上下文中运行MainProxy → Implementation 用delegatecall模块逻辑代码在MainProxy 的存储上下文中执行读写的是 MainProxy 上的状态从而实现状态集中、逻辑分散。日志事件的分模块记录模块代理模式中另一个精彩的设计是event log 记录在模块的 Proxy 中而不是堆叠在 MainProxy 上。原文档指出在 fallback 中有一个条件语句当使用模块中类似emitViaProxy_Transfer的函数发出 event log 时日志会被分门别类地记录在不同模块的 Proxy 中而不会全部堆叠在 MainProxy 中。对应到 Proxy.md 的源码fallback 的第一个分支正是处理这种场景——当msg.sender creator_即调用来自 MainProxy 内部模块时直接解析 calldata 并以模块 Proxy 自身的地址发出日志if (msg.sender creator_) { assembly { mstore(0, 0) calldatacopy(31, 0, calldatasize()) switch mload(0) // numTopics case 0 { log0(32, sub(calldatasize(), 1)) } case 1 { log1(64, sub(calldatasize(), 33), mload(32)) } case 2 { log2(96, sub(calldatasize(), 65), mload(32), mload(64)) } case 3 { log3(128, sub(calldatasize(), 97), mload(32), mload(64), mload(96)) } case 4 { log4(160, sub(calldatasize(), 129), mload(32), mload(64), mload(96), mload(128)) } default { revert(0, 0) } return(0, 0) } }该分支先读取 calldata 首部的numTopics日志主题数量再根据 04 个 topic 的情况分别调用log0log4操作码在当前 Proxy 合约地址上写出日志。这解决了可升级合约场景中的一个典型问题若模块 Implementation 直接emit事件由于delegatecall的特性日志的address字段会是 MainProxy 地址导致所有模块的日志全部堆叠在 MainProxy 上难以区分归属Euler 通过emitViaProxy_Transfer、emitViaProxy_Approval这类辅助函数把日志数据打包成 calldata 再回拨给对应的 ModuleProxy让日志以各模块自己的地址发出从而将不同模块如每个资产的 EToken/DToken的事件分门别类地归档在各自 Proxy 下便于链上索引与应用层解析。模块化代理设计的优势与权衡综合原文档与仓库源码可以总结出 Euler 模块代理模式的核心收益模块独立升级9 个模块各司其职单个模块的 Implementation 可独立替换无需整体迁移逻辑合约升级风险被有效收敛见 contracts/README.md状态集中管理所有协议状态都在 MainProxyEuler.sol上模块安装/升级不影响既有状态trustedSenders、moduleLookup、proxyLookup等映射让状态与模块的对应关系清晰可查日志按模块归档emitViaProxy_*机制让事件日志归属到各模块 Proxy而不是在 MainProxy 上混成一团版本特征可追踪moduleId模块固定编号与moduleGitCommits版本标记共同构成模块的版本指纹便于审计比对。与此同时从代码结构看这一设计也有明确的代价调用链路长用户请求需要经过ModuleProxy → MainProxy.dispatch → Implementation两跳转发相比单层代理多了一次外部调用calldata 重组带来额外 gas每次调用都要在 assembly 中拼接 selector、calldata 与 caller 地址且日志转发需要额外的内联汇编处理实现复杂度高trustedSenders鉴权、长度校验、日志 topic 分支等细节都需要在底层 assembly 中小心处理这对工程实现与安全审计提出了更高要求。延伸阅读模块代理模式文档本文主体来源含架构与流程图示说明Proxy 源码级分析Proxy合约构造函数与完整 fallback assembly 源码BaseLogic 源码级分析_createProxy、trustedSenders、proxyLookup等模块代理底层实现Euler 合约指南9 个模块清单与模块化升级特点总结Markets 源码级分析doActivateMarket中复用_createProxy创建 EToken/DToken 代理的实例Euler 攻击复盘以eToken.deposit等操作的 sequence 图直观展示模块代理的完整调用链Euler 协议导读Euler 借贷协议的整体原理、流动性计算与清算机制背景。赞分享示例工程区块链【免费下载链接】Dapp-LearningDapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.项目地址https://gitcode.com/gh_mirrors/da/Dapp-Learning点击查看免费下载相关推荐Euler 合约九大模块与模块化可升级架构深度解析Euler 合约九大模块与模块化可升级架构深度解析 本文以 defi/Euler/contracts/README.md https://link.gitcod示例工程区块链WTF Solidity 代理合约Proxy Contract详解用 delegatecall 实现可升级与省 Gas 的合约架构WTF Solidity 代理合约Proxy Contract详解用 delegatecall 实现可升级与省 Gas 的合约架构 导读 智能合约一旦部署示例工程区块链教程SolidityPy全课程可升级合约代理模式终极指南SolidityPy全课程可升级合约代理模式终极指南 SolidityPy全课程是一个从入门到专家的完整区块链开发教程专注于Solidity智能合约与Pyt区块链教育上一篇Mole Mac 深度清理使用指南5 分钟拿回好几个 GB 磁盘空间下一篇Responsive Elements高级配置指南自定义断点、间隔与性能优化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/13 9:20:28

SQL字符串精准定位与条件填充实战:POSITION+SUBSTRING+CASE WHEN

1. 这不是“查数据”,而是“精准定位智能填充”的组合拳SQL里最常被低估的,其实是字段级的字符串操作能力。很多人一看到“从指定字段查找指定字符”,第一反应是LIKE加%模糊匹配——这就像用消防水枪给盆栽浇水,方向对了&#xff…

2026/10/13 9:20:28

Python+MySQL学生选课管理系统:从数据库设计到答辩避坑全指南

简介:基于Python和MySQL实现的学生选课管理系统,是一份面向计算机相关专业期末大作业或毕业设计的完整项目包。项目由导师指导并通过高分评审,源码均经过本地编译调试,可稳定运行,适合需要快速构建课程设计或提升实战能…

2026/10/13 10:30:59

RPA技术在写字楼电梯消毒管理中的应用实践

1. 项目背景与痛点分析写字楼电梯作为密闭高频接触空间,其卫生状况直接关系到办公人群的健康安全。在常态化防疫背景下,传统人工消毒管理存在三大典型问题:执行遗漏风险:保洁人员需同时负责多个区域,容易因工作量大而遗…

2026/10/13 10:30:59

基于Node.js+Vue的农业电商平台架构设计与实践

1. 项目概述这个农业电商平台项目采用Node.jsVue技术栈构建,面向果蔬种植户和消费者提供从生产到销售的全流程服务。我在开发过程中发现,农产品电商与传统电商存在显著差异——需要特别关注商品保鲜期、物流时效、产地溯源等农业特性。平台包含商家管理后…

2026/10/13 10:30:59

MySQL 5.7数据完整性实战:五类约束添加与避坑指南

1. 为什么“数据完整性”不是一句空话,而是MySQL里每天都在发生的生死线你有没有遇到过这样的情况:业务系统里突然冒出一批“张三”用户,身份证号全是123456789012345678,注册时间统一显示为1970-01-01?或者财务报表里…

2026/10/13 10:30:59

SpringBoot助农电商系统架构设计与实践

1. 项目背景与核心价值最近几年农产品电商平台如雨后春笋般涌现,但真正能解决农民卖货难、消费者买真货难的平台却不多。这个基于SpringBoot的助农商城项目,正是瞄准了这个市场痛点。我在实际开发过程中发现,传统的农产品交易存在三大核心问题…

2026/10/13 10:25:58

小白程序员也能进阶大模型,高薪岗位等你来!

本文讨论了AI行业的发展趋势,指出虽然核心算法岗竞争激烈,但AI大模型应用开发工程师因其高薪、市场需求大、门槛适中而成为热门选择。文章强调,随着各行各业对AI的投入增加,AI大模型应用开发的需求将持续增长,为普通人…

2026/10/13 5:19:27

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/13 0:04:41

基于Triton部署YOLO目标检测:从训练脚本到线上服务

简介:这份资源面向希望将目标检测模型落地到生产环境的深度学习开发者与算法工程师,聚焦于用 Triton Inference Server 部署 YOLOv9 的完整实战方案,解决从训练权重到线上推理服务之间的工程化难题。压缩包共 16 个文件,约 886KB&…

2026/10/13 0:04:41

手把手教你云端部署Qwen-Image-2.1:从GPU选型到在线文生图服务

Qwen-Image-2.1 这个开源的图像生成模型,最近在内容创作和 AI 应用圈子里热度相当高。它属于通义 Qwen 系列里的视觉生成分支,让我眼前一亮的是它对中文长文本的理解能力——以前用那些老方案生成带中文文字的图,十个里九个是“乱码印刷体”&…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑