
示例工程区块链【免费下载链接】Dapp-LearningDapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.项目地址https://gitcode.com/gh_mirrors/da/Dapp-Learning点击查看免费下载本文基于 Dapp-Learning 仓库中 defi/Euler/contracts/BaseLogic.md 展开结合仓库内 Euler 协议总览、模块代理模式、RiskManager、Markets 等文档与源码片段系统拆解 Euler 借贷协议的公共逻辑层BaseLogic——它是所有模块共用的基础工具集模块代理创建、子账户地址派生、市场进入管理、资产配置解析、利息累积与平均流动性核算。读完本文你将理解 Euler 如何用按位异或实现子账户、如何通过 Proxy 委托调用完成模块化升级、以及 TWAP 定价与利息复利累积在底层合约中的落地方式。一、BaseLogic 在 Euler 中的定位Euler 是一个模块化的去中心化借贷协议其合约并非单一大合约而是由一个主入口合约Euler.sol存储全部状态加若干功能模块组成。根据 defi/Euler/contracts/README.mdEuler 一共 9 个模块DToken债务凭证Etoken抵押凭证Exec执行合约可批量执行操作Governance治理模块Installer模块安装器涉及模块的设置和权限Liquidiation清算模块Markets资产市场存储资产在 Euler 借贷市场中的相关参数例如BorrowFactor、CollateralFactorRiskManager风险管理模块Swap交易模块用户 EToken 的交易不是 underlyingBaseLogic是这些模块共享的公共逻辑层它不负责某一具体业务而是提供各模块都会复用的基础设施函数例如创建模块代理、解析资产配置、刷新利息累积器、更新用户平均流动性等。正因为状态与逻辑分离、逻辑再被各模块复用Euler 才能实现按需分模块升级的模块化可升级架构详见 defi/Euler/ModuleProxyMode.md。二、模块代理创建_createProxy各模块都通过_createProxy函数创建自己的 Proxy 合约实例。该函数在 defi/Euler/contracts/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且不能超过MAX_EXTERNAL_MODULEID。模块 ID 分为内部模块与外部模块两类内部模块如RiskManager不在创建时暴露给外部调用。单实例缓存对于MAX_EXTERNAL_SINGLE_PROXY_MODULEID以内的模块一个模块只对应一个 Proxy 实例已创建过就直接从proxyLookup返回避免重复部署。以 defi/Euler/contracts/Proxy.md 中展示的 Euler 构造函数为例Installer模块部署完成后Euler.sol构造函数即调用_createProxy(MODULEID__INSTALLER)为其创建代理并把trustedSenders[installerProxy].moduleImpl绑定到 Installer 实现合约。信任登记新代理地址被写入trustedSenders记录其moduleIdmoduleImpl稍后由 Installer 在安装实现合约时填充。这构成了只有被信任的模块代理发来的调用才会被执行的信任边界。在 Markets 模块 的doActivateMarket中可以看到它的典型调用方式激活一个新资产市场时分别用_createProxy(MODULEID__ETOKEN)与_createProxy(MODULEID__DTOKEN)为该资产创建抵押凭证与债务凭证两个代理。2.1 模块代理的委托调用链模块 Proxy 本身几乎没有任何业务函数所有调用都会落入 fallback。根据 defi/Euler/contracts/Proxy.md 的剖析Proxy 的 fallback 做了两个分支若调用者是 creator即主入口合约Euler.sol说明是模块内部通过emitViaProxy_Transfer/emitViaProxy_Approval等方式发日志fallback 直接用log0~log4内联汇编将该事件的 topic 与 data 记录在该模块自己的 Proxy 地址上而不是堆叠在主入口合约上若是外部用户调用fallback 会将 calldata 重组为dispatch selector calldata caller address再用call调用主入口的dispatch()dispatch再以delegatecall执行对应模块 Implementation 的逻辑。这样设计的精妙之处在于函数运行逻辑在模块 Implementation 合约中而读写的状态全部在主入口合约Euler.sol上因此模块的安装与升级不会影响协议既有状态。该调用链的全流程示意见 defi/Euler/ModuleProxyMode.md含部署、安装/升级、用户调用三条流程的图示。三、子账户体系按位异或生成、按位或校验大多数借贷协议用 mapping 或数组在主账户下罗列子账户序号Euler 则采用更省 gas 的地址派生方案不存储任何子账户映射而是直接通过按位异或XOR运算从主账户地址生成子账户地址校验隶属关系时再用按位或OR运算判断。以 defi/Euler/contracts/BaseLogic.md 中的示例假设地址为 uint32 类型的0x45说明// primary account 0x45 // subaccounts 0x45 ^ 0x0 // 0x45 1000101 subaccount 0 as primary 0x45 ^ 0x1 // 0x44 1000100 subaccount 1 0x45 ^ 0x2 // 0x47 1000111 subaccount 2 0x45 ^ 0x3 // 0x46 1000110 subaccount 3 ... 0x45 ^ 0xF // 0x4A 1001010 subaccount 15 // subacounts verify get same result 0x45 | 0xF // 0x4F 1001111 0x44 | 0xF // 0x4F 1001111 // not subaccount verify get different result 0x3d | 0xF // 0x3F 111111对应源码function getSubAccount(address primary, uint subAccountId) internal pure returns (address) { require(subAccountId 256, e/sub-account-id-too-big); return address(uint160(primary) ^ uint160(subAccountId)); } function isSubAccountOf(address primary, address subAccount) internal pure returns (bool) { return (uint160(primary) | 0xFF) (uint160(subAccount) | 0xFF); }原理拆解地址空间划分一个地址的低 8 位0xFF被用来编码子账户序号因此一个主账户最多拥有 256 个子账户subAccountId从 0 到 255校验subAccountId 256。subAccountId 0即主账户本身。生成primary ^ subAccountId只翻转低 8 位得到该序号对应的子账户地址完全确定、无需存储。校验primary | 0xFF与subAccount | 0xFF都等于把地址低 8 位置 1的结果。只要subAccount是由primary通过某个0~255序号异或得到的两者按位或的结果必然一致而无关地址如示例中的0x3d按位或后结果不同判定为不属于该主账户。这种方案在 Liquidation 模块 中也有应用computeLiqOpp第一步就用isSubAccountOf(violator, liquidator)拒绝自己清算自己的子账户e/liq/self-liquidation。四、已进入市场列表getEnteredMarketsArray在 Euler 中用户需要进入某个市场后才能在该资产上进行抵押与借贷参见 Markets 模块 的doActivateMarket。getEnteredMarketsArray负责把用户在marketsEntered[account]中的固定长度存储数组转换为按实际进入数量裁剪的动态数组function getEnteredMarketsArray(address account) internal view returns (address[] memory) { uint32 numMarketsEntered accountLookup[account].numMarketsEntered; address firstMarketEntered accountLookup[account].firstMarketEntered; address[] memory output new address[](numMarketsEntered); if (numMarketsEntered 0) return output; address[MAX_POSSIBLE_ENTERED_MARKETS] storage markets marketsEntered[account]; output[0] firstMarketEntered; for (uint i 1; i numMarketsEntered; i) { output[i] markets[i]; } return output; }值得注意的两个设计首个市场单独存储firstMarketEntered单独存在accountLookup[account]上而不放在marketsEntered数组里。结合numMarketsEntered计数可以避免在用户只进入 1 个市场时读写整个数组节省存储布局。返回动态数组函数返回address[] memory把存储数组拷贝为与numMarketsEntered等长的内存数组供上层如RiskManager.computeLiquidity遍历使用。MAX_POSSIBLE_ENTERED_MARKETS是存储数组的固定容量上限。该函数是流动性计算的数据入口RiskManager.computeLiquidity正是先调用getEnteredMarketsArray(account)拿到用户进入的全部市场再逐个资产计算抵押与负债价值详见 RiskManager.md。五、资产配置解析resolveAssetConfig每个市场的资产配置AssetConfig保存在underlyingLookup[underlying]中resolveAssetConfig负责取出配置并填充默认值uint32 internal constant DEFAULT_BORROW_FACTOR uint32(0.28 * 4_000_000_000); uint24 internal constant DEFAULT_TWAP_WINDOW_SECONDS 30 * 60; function resolveAssetConfig(address underlying) internal view returns (AssetConfig memory) { AssetConfig memory config underlyingLookup[underlying]; // If address is zero, means config havent initialized yet require(config.eTokenAddress ! address(0), e/market-not-activated); if (config.borrowFactor type(uint32).max) config.borrowFactor DEFAULT_BORROW_FACTOR; if (config.twapWindow type(uint24).max) config.twapWindow DEFAULT_TWAP_WINDOW_SECONDS; return config; }要点说明激活校验若eTokenAddress为address(0)说明该资产市场尚未激活直接 reverte/market-not-activated。激活流程见 Markets.md激活时会设置underlyingLookup[underlying] params.config并创建 EToken/DToken 代理。默认值机制AssetConfig的某些字段用type(uint32).max/type(uint24).max表示未显式设置resolveAssetConfig在读取时替换为默认值。这样做的好处是治理设置配置时无需填充所有字段未设置的自动回落默认值。负债系数borrowFactor它是真实负债资产与负债资产调整值之间的除数即liability risk-adjustment assetValue / borrowFactor。默认值DEFAULT_BORROW_FACTOR 0.28数值上0.28 * 4_000_000_000是因为CONFIG_FACTOR_SCALE 4_000_000_000见 RiskManager.md。TWAP 窗口期默认 30 分钟30 * 60秒用于 Uniswap V3 TWAP 预言机的时间窗口。值得注意的是新市场激活时 RiskManager 给出的默认参数极为保守最低安全权限borrowIsolated true、collateralFactor 0、borrowFactor type(uint32).max、twapWindow type(uint24).max这正是为了让resolveAssetConfig的默认值机制接管见 RiskManager.md。六、资产缓存AssetCache与利息累积6.1 结构体assetCacheAssetCache是 Euler 中每个资产池在单次操作内的内存快照把 storage 中分散的字段一次性载入 memory减少重复 SLOAD。结构如下struct AssetCache { address underlying; uint112 totalBalances; uint144 totalBorrows; uint96 reserveBalance; uint interestAccumulator; uint40 lastInterestAccumulatorUpdate; uint8 underlyingDecimals; uint32 interestRateModel; int96 interestRate; uint32 reserveFee; uint16 pricingType; uint32 pricingParameters; uint poolSize; // result of calling balanceOf on underlying (in external units) uint underlyingDecimalsScaler; uint maxExternalAmount; }各字段含义underlying资产合约地址totalBalances/totalBorrows池内总抵押EToken 总量内部精度与总债务DToken 总量reserveBalance准备金余额来自借款利息按reserveFee计提的部分interestAccumulator利息累积器复利累积的核心变量lastInterestAccumulatorUpdate上次更新利息累积器的时间戳uint40underlyingDecimals底层代币精度underlyingDecimalsScaler 10^(18 - decimals)用于把余额统一到 1e18 内部精度interestRateModel/interestRate当前使用的利率模型模块 ID 与当前利率reserveFee准备金费率pricingType/pricingParameters定价方式与参数如 Uniswap V3 池费率档位poolSize调用balanceOf得到的池内底层资产余额外部单位maxExternalAmountMAX_SANE_AMOUNT / underlyingDecimalsScaler用于防溢出上限检查。6.2 初始化与利息累积initAssetCacheinitAssetCache将 storage 载入缓存统一精度并完成利息与准备金的复利更新。核心流程与 defi/Euler/contracts/BaseLogic.md 一致Storage 载入将lastInterestAccumulatorUpdate、underlyingDecimals、interestRateModel、interestRate、reserveFee、pricingType、pricingParameters、reserveBalance、totalBalances、totalBorrows、interestAccumulator等字段读入缓存。精度统一underlyingDecimalsScaler 10**(18 - underlyingDecimals)maxExternalAmount MAX_SANE_AMOUNT / underlyingDecimalsScaler再调用callBalanceOf得到poolSize若poolSize maxExternalAmount则poolSize * scaler统一为 1e18 精度否则置 0防溢出保护。复利累积当block.timestamp ! lastInterestAccumulatorUpdate时标记dirty true并计算deltaT block.timestamp - lastInterestAccumulatorUpdatenewInterestAccumulator RPow.rpow(interestRate 1e27, deltaT, 1e27) * interestAccumulator / 1e27即按(1 rate)^deltaT复利增长1e27为利率的定点数基数newTotalBorrows totalBorrows * newInterestAccumulator / interestAccumulator债务按累积器比例同步膨胀准备金计提feeAmount (newTotalBorrows - totalBorrows) * (reserveFee type(uint32).max ? DEFAULT_RESERVE_FEE : reserveFee) / (RESERVE_FEE_SCALE * INTERNAL_DEBT_PRECISION)若有计提则newTotalBalances poolAssets * newTotalBalances / (poolAssets - feeAmount)newReserveBalance newTotalBalances - totalBalances——准备金以稀释 EToken 持有者份额的方式计入而非凭空增发。溢出检查与回写仅当newTotalBalances MAX_SANE_AMOUNT newTotalBorrows MAX_SANE_DEBT_AMOUNT时才将新值写入缓存encodeDebtAmount/encodeAmount/encodeSmallAmount为精度编码并把lastInterestAccumulatorUpdate更新为uint40(block.timestamp)。function initAssetCache(address underlying, AssetStorage storage assetStorage, AssetCache memory assetCache) internal view returns (bool dirty) { dirty false; assetCache.underlying underlying; // Storage loads assetCache.lastInterestAccumulatorUpdate assetStorage.lastInterestAccumulatorUpdate; uint8 underlyingDecimals assetCache.underlyingDecimals assetStorage.underlyingDecimals; assetCache.interestRateModel assetStorage.interestRateModel; assetCache.interestRate assetStorage.interestRate; assetCache.reserveFee assetStorage.reserveFee; assetCache.pricingType assetStorage.pricingType; assetCache.pricingParameters assetStorage.pricingParameters; assetCache.reserveBalance assetStorage.reserveBalance; assetCache.totalBalances assetStorage.totalBalances; assetCache.totalBorrows assetStorage.totalBorrows; assetCache.interestAccumulator assetStorage.interestAccumulator; // Derived state unchecked { assetCache.underlyingDecimalsScaler 10**(18 - underlyingDecimals); assetCache.maxExternalAmount MAX_SANE_AMOUNT / assetCache.underlyingDecimalsScaler; } uint poolSize callBalanceOf(assetCache, address(this)); if (poolSize assetCache.maxExternalAmount) { unchecked { assetCache.poolSize poolSize * assetCache.underlyingDecimalsScaler; } } else { assetCache.poolSize 0; } // Update interest accumulator and reserves if (block.timestamp ! assetCache.lastInterestAccumulatorUpdate) { dirty true; uint deltaT block.timestamp - assetCache.lastInterestAccumulatorUpdate; // Compute new values uint newInterestAccumulator (RPow.rpow(uint(int(assetCache.interestRate) 1e27), deltaT, 1e27) * assetCache.interestAccumulator) / 1e27; uint newTotalBorrows assetCache.totalBorrows * newInterestAccumulator / assetCache.interestAccumulator; uint newReserveBalance assetCache.reserveBalance; uint newTotalBalances assetCache.totalBalances; uint feeAmount (newTotalBorrows - assetCache.totalBorrows) * (assetCache.reserveFee type(uint32).max ? DEFAULT_RESERVE_FEE : assetCache.reserveFee) / (RESERVE_FEE_SCALE * INTERNAL_DEBT_PRECISION); if (feeAmount ! 0) { uint poolAssets assetCache.poolSize (newTotalBorrows / INTERNAL_DEBT_PRECISION); newTotalBalances poolAssets * newTotalBalances / (poolAssets - feeAmount); newReserveBalance newTotalBalances - assetCache.totalBalances; } // Store new values in assetCache, only if no overflows will occur if (newTotalBalances MAX_SANE_AMOUNT newTotalBorrows MAX_SANE_DEBT_AMOUNT) { assetCache.totalBorrows encodeDebtAmount(newTotalBorrows); assetCache.interestAccumulator newInterestAccumulator; assetCache.lastInterestAccumulatorUpdate uint40(block.timestamp); if (newTotalBalances ! assetCache.totalBalances) { assetCache.reserveBalance encodeSmallAmount(newReserveBalance); assetCache.totalBalances encodeAmount(newTotalBalances); } } } }返回的dirty布尔值用于提示调用方本次操作是否刷新过利息从而决定是否需要把缓存写回 storage。七、利率模型updateInterestRateEuler 的利率根据资金利用率utilisation计算且利率存在上下限MAX_ALLOWED_INTEREST_RATE与MIN_ALLOWED_INTEREST_RATE。利率模型是分段函数折线由专门的利率模型模块如BaseIRMLinearKink实现BaseLogic.updateInterestRate只负责计算利用率并委托调用利率模型// BaseLogic.sol function updateInterestRate(AssetStorage storage assetStorage, AssetCache memory assetCache) internal { uint32 utilisation; { uint totalBorrows assetCache.totalBorrows / INTERNAL_DEBT_PRECISION; uint poolAssets assetCache.poolSize totalBorrows; if (poolAssets 0) utilisation 0; // empty pool arbitrarily given utilisation of 0 else utilisation uint32(totalBorrows * (uint(type(uint32).max) * 1e18) / poolAssets / 1e18); } bytes memory result callInternalModule(assetCache.interestRateModel, abi.encodeWithSelector(BaseIRM.computeInterestRate.selector, assetCache.underlying, utilisation)); (int96 newInterestRate) abi.decode(result, (int96)); assetStorage.interestRate assetCache.interestRate newInterestRate; }利用率计算utilisation totalBorrows / (poolSize totalBorrows)即借款总额占池内资产 借款总额的比例空池任意取 0。换算为uint32定点数type(uint32).max作为 1e18 基数的满刻度。模块调用通过callInternalModule以computeInterestRate选择器调用interestRateModel模块获得int96格式的新利率。上下限裁剪BaseIRM.computeInterestRate在调用实现函数后裁剪// BaseIRM.sol function computeInterestRate(address underlying, uint32 utilisation) external returns (int96) { int96 rate computeInterestRateImpl(underlying, utilisation); if (rate MAX_ALLOWED_INTEREST_RATE) rate MAX_ALLOWED_INTEREST_RATE; else if (rate MIN_ALLOWED_INTEREST_RATE) rate MIN_ALLOWED_INTEREST_RATE; return rate; } // BaseIRMLinearKink function computeInterestRateImpl(address, uint32 utilisation) internal override view returns (int96) { uint ir baseRate; if (utilisation kink) { ir utilisation * slope1; } else { ir kink * slope1; ir slope2 * (utilisation - kink); } return int96(int(ir)); }BaseIRMLinearKink是典型的线性拐点模型利用率低于kink时利率以slope1斜率线性增长低利用率、低利率区间超过kink后换用更陡的slope2斜率激励借款人尽快还款、抑制资金池过度使用。利率的上下限裁剪保证了极端情况下利率不会脱离合理区间。八、流动性核算时间加权平均流动性BaseLogic中关于流动性的三个函数构成了用户平均流动性的完整核算链路是 清算 与健康系数计算的基础。8.1updateAverageLiquidity更新用户在时间窗口内的平均流动性。平均流动性定义为抵押资产价值减去负债资产价值时间窗口AVERAGE_LIQUIDITY_PERIOD 24 * 60 * 6024 小时function updateAverageLiquidity(address account) internal { uint lastAverageLiquidityUpdate accountLookup[account].lastAverageLiquidityUpdate; if (lastAverageLiquidityUpdate 0) return; uint deltaT block.timestamp - lastAverageLiquidityUpdate; if (deltaT 0) return; accountLookup[account].lastAverageLiquidityUpdate uint40(block.timestamp); accountLookup[account].averageLiquidity computeNewAverageLiquidity(account, deltaT); }流程三步走若lastAverageLiquidityUpdate 0说明该用户此前从未添加过流动性无抵押资产不更新计算上次更新到当前时间的deltaT若为 0同一区块内重复触发不更新刷新更新时间戳并调用computeNewAverageLiquidity重新计算平均流动性。例如在 EToken 的 mint 流程 中第一步就是updateAverageLiquidity(account)保证任何借贷行为前用户的平均流动性都是最新的。8.2computeNewAverageLiquidity按时间加权算法计算窗口内平均流动性AVERAGE_LIQUIDITY_PERIOD prevDuration currDuration NewAverageLiquidity (prevDuration * prevAverageLiquidity currDuration * currAverageLiquidity) / AVERAGE_LIQUIDITY_PERIODuint internal constant AVERAGE_LIQUIDITY_PERIOD 24 * 60 * 60; function computeNewAverageLiquidity(address account, uint deltaT) private returns (uint) { uint currDuration deltaT AVERAGE_LIQUIDITY_PERIOD ? AVERAGE_LIQUIDITY_PERIOD : deltaT; uint prevDuration AVERAGE_LIQUIDITY_PERIOD - currDuration; uint currAverageLiquidity; { (uint collateralValue, uint liabilityValue) getAccountLiquidity(account); currAverageLiquidity collateralValue liabilityValue ? collateralValue - liabilityValue : 0; } return (accountLookup[account].averageLiquidity * prevDuration / AVERAGE_LIQUIDITY_PERIOD) (currAverageLiquidity * currDuration / AVERAGE_LIQUIDITY_PERIOD); }逻辑拆解currDuration为本次更新的时间跨度且最大不超过窗口 24 小时deltaT超过窗口则截断prevDuration AVERAGE_LIQUIDITY_PERIOD - currDuration即上一次快照在窗口内还占用的时间当前流动性currAverageLiquidity即抵押价值超出负债价值的部分最小为 0资不抵债时按 0 计最终结果是新旧两段流动性按各自持续时长加权的平均值——既不是瞬时值也不是简单滑动平均而是滚动窗口内的时间加权平均。8.3getAccountLiquidity计算用户当前的抵押资产价值与负债资产价值委托给RiskManager模块的computeLiquidityfunction getAccountLiquidity(address account) internal returns (uint collateralValue, uint liabilityValue) { bytes memory result callInternalModule(MODULEID__RISK_MANAGER, abi.encodeWithSelector(IRiskManager.computeLiquidity.selector, account)); (IRiskManager.LiquidityStatus memory status) abi.decode(result, (IRiskManager.LiquidityStatus)); collateralValue status.collateralValue; liabilityValue status.liabilityValue; }RiskManager.computeLiquidity的完整实现见 defi/Euler/contracts/RiskManager.md。其核心思想更详细的推导见 defi/Euler/LeverageLendingAlgorithmOfEuler.md遍历getEnteredMarketsArray(account)返回的每个市场抵押价值 数量 × 价格 ×collateralFactor负债价值 数量 × 价格 /borrowFactorEuler 在 Compound 的Collateral Factor基础上引入Borrow Factor使不同资产的风险调整更灵活Collateral用CF值 × CF调整Self-Collateral用固定SCF 0.95调整Liabilities用BF值 / BF调整Self-Liability用固定SBF 1调整由于 mint 操作会生成等值的Self-Collateral与Self-Liability计算时先假设某资产的全部债务都是Self-Liability反推Self-Collateral Self-Liability / 0.95若反推出的抵押超过实际抵押数量则把Self-Collateral重设为实际抵押超出部分计入常规Liabilities。最终Liquidity Collateral_RA - Liabilities_RA当抵押与负债的风险调整价值相等时HealthScore 1处于可清算临界点。该机制同时支持最高 19 倍杠杆的 mint 操作与杠杆做空详细数值案例见 Euler 协议总览 与 杠杆借贷算法。下图对比了 Compound 与 Euler 的流动性计算模型8.4 平均流动性在清算中的应用用户的时间加权平均流动性并非仅为统计用途它直接参与清算折扣的定价。在 Liquidation 模块 的computeLiqOpp中baseDiscount UNDERLYING_RESERVES_FEE (1e18 - healthScore)健康系数越低基础折扣越大computeDiscountBooster(liquidator, liabilityValue)用清算人 24 小时平均流动性除以被清算人负债价值得到一个 1e18 精度的乘数清算人提供的平均流动性越多享受的清算折扣越大booster上限为 1e18即不会超过基础折扣加MAXIMUM_BOOSTER_DISCOUNT最终折扣discount baseDiscount * discountBooster / 1e18并受MAXIMUM_DISCOUNT上限约束。清算时以把被清算人健康系数恢复到目标值TARGET_HEALTH1.25为基准计算应偿还的债务repay与应获得的抵押yield且repay中还要扣除UNDERLYING_RESERVES_FEE的准备金费。这与传统固定折扣 优先 gas 拍卖不同形成了一种接近荷兰式拍卖的清算机制详见 defi/Euler/README.md 与 defi/Euler/contracts/Liquidation.md。九、价格预言机背景BaseLogic中resolveAssetConfig提到的twapWindow与AssetCache中的pricingType/pricingParameters均服务于价格获取。根据 RiskManager.md 与 预言机说明Euler 的定价体系为PRICINGTYPE__PEGGED 1锚定类型参考资产当前为 WETH价格恒为 1PRICINGTYPE__UNISWAP3_TWAP 2从该资产与 WETH 组成的 Uniswap V3 池读取 TWAP 价格pricingParameters存储池子费率档位新市场激活时自动遍历[3000, 10000, 500, 100]四档费率选取流动性最好的池子见 RiskManager.mdPRICINGTYPE__FORWARDED 3PToken 转发定价。由于 TWAP 价格依赖 Uniswap V3 池的流动性引用流动性差的池子存在被操纵的风险——项目方因此建立了从 A 到 F 的预言机风险评级体系任何低于 B 的资产都建议避免使用详见 defi/Euler/oracle/readme.md。十、总结BaseLogic 的设计启示BaseLogic虽名为公共逻辑却浓缩了 Euler 协议最核心的工程与算法决策模块化代理通过_createProxy统一创建模块代理、登记trustedSenders结合 Proxy fallback 的call → dispatch → delegatecall调用链实现逻辑分散、状态集中的分模块升级架构子账户零存储用 XOR 派生地址、OR 校验隶属256 个子账户无需任何 mapping 存储精度与防溢出统一 1e18 内部精度、MAX_SANE_AMOUNT上限、encodeAmount编码在复利累积与流动性计算中全程防溢出复利与准备金initAssetCache一次性完成利息累积、债务膨胀、准备金计提并用dirty标记控制回写时间加权平均流动性24 小时滚动窗口 新旧加权既避免瞬时波动干扰清算定价又避免为每个用户存储多条历史快照。对于希望深入理解模块化借贷协议实现的开发者建议按 defi/Euler/ModuleProxyMode.md → defi/Euler/contracts/Proxy.md → defi/Euler/contracts/RiskManager.md → defi/Euler/contracts/Markets.md 的顺序阅读即可串联起代理架构 → 市场激活 → 资产缓存与利息 → 流动性计算的完整链路。赞分享示例工程区块链【免费下载链接】Dapp-LearningDapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.项目地址https://gitcode.com/gh_mirrors/da/Dapp-Learning点击查看免费下载相关推荐Transformer-XL应用场景探索从文本生成到代码补全的10个用例Transformer XL应用场景探索从文本生成到代码补全的10个用例 Transformer XL是一种突破性的语言模型它通过引入循环机制和记忆增强技术深度学习NLP大模型Diem 区块链账户冻结机制深度解析AccountFreezing 模块源码与实战指南Diem 区块链账户冻结机制深度解析AccountFreezing 模块源码与实战指南 账户冻结Account Freezing是合规型区块链网络的必备能区块链金融科技Diem Move 框架源码解读AccountLimits 账户限额模块与滑动时间窗口机制Diem Move 框架源码解读AccountLimits 账户限额模块与滑动时间窗口机制 0x1::AccountLimits 是 Diem原 Libra区块链金融科技上一篇exo多台设备组AI集群RDMA让大模型跑得更快下一篇Roc 类型系统探秘嵌套函数注解中的类型变量遮蔽Type Variable Shadowing与编译器快照测试验证创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考