Yieldoor 协议 Gas 优化

  • Dacian
  • 发布于 2025-03-18 19:33
  • 阅读 24

这篇文章通过一个实际案例研究了如何通过重构优化 Solidity 代码来实现气体节省,展示了在一个杠杆收益农场协议 Yieldoor 中,通过改进核心函数 Leverager::liquidatePosition 实现了 15.43% 的气体节约。作者详细描述了测量Gas成本的方法和重构过程,包括减少冗余存储读取、使用结构体缓存、以及启用优化器以提高代码效率。

Decorated 审计师 deadrosesxyz 最近戴上了开发者的帽子,创建了一个杠杆收益农场协议 Yieldoor,其代码提供了大量的gas优化机会。许多文章列出了常见的gas优化技术,但这个真实案例研究展示了通过逐步的思维过程重构,优化核心协议功能而实现的15.43%的整体gas节省。

我们将使用公开可用的竞赛 源代码,并先专注于最有趣、最复杂和关键的清算函数 Leverager::liquidatePosition

测量 Gas成本

Foundry 提供了多种内置工具来测量 gas 成本,包括:

  • 通过在 foundry.toml 中配置合约部署和函数调用成本后的 forge test --gas-report 来获取 gas 报告

  • 通过 forge snapshot 获取整个单元测试套件的 gas 成本的 测试函数快照

  • 通过 cheatcodes 来测量单元测试中任意代码段的 gas 成本的 gas区块快照

我们将 分叉 原始源代码,并使用第二和第三个选项:

  • 编辑 test/Leverager.t.sol,添加 test_Gas_* 前缀环绕三个调用 Leverager::liquidatePosition 的测试

  • 通过将相关注释编辑为 /// forge-config: default.fuzz.runs = 100 来改变 test/BaseTest.sol,减少模糊运行次数至100,以加快迭代优化周期

  • 在测试函数内部添加 “gas 区块快照”,围绕对 Leverager::liquidatePosition 的调用:

vm.startSnapshotGas("lendingPoolLiquidation");
ILeverager(leverager).liquidatePosition(liqParams);
vm.stopSnapshotGas();

vm.startSnapshotGas("liquidateWithSwap");
ILeverager(leverager).liquidatePosition(liqParams);
vm.stopSnapshotGas();

vm.startSnapshotGas("liquidationNoSwap");
ILeverager(leverager).liquidatePosition(liqParams);
vm.stopSnapshotGas();
{
  "lendingPoolLiquidation": "575367",
  "liquidateWithSwap": "618080",
  "liquidationNoSwap": "439622"
}

将这些文件备份到 .gas-snapshot.originalsnapshots/LeveragerTest.original.json 以保持一个起始点记录,方便在最后进行比较。任何代码变更后,我们都可以运行:

  • forge snapshot --diff --fork-url ETH_RPC_URL --fork-block-number 20956198 以查看变更是否减少了每个测试和所有测试的整体 gas 成本。要覆盖现有的 .gas-snapshot 文件,只需在不带 -diff 标志的情况下运行它。重要提示:生成新的 .gas-snapshot 文件必须确保所有测试通过

  • forge test --gas-snapshot-check=true --match-test test_Gas --fork-url ETH_RPC_URL --fork-block-number 20956198 以查看变更是否减少了手动快照的 gas 成本。若要覆盖现有文件,只需在运行此命令之前通过 rm snapshots/LeveragerTest.json 删除旧文件

缓存相同存储读取

通常,最简单、最具效益的 gas 优化是缓存相同的存储读取,以防止重复从存储中读取相同的值。这可以通过以下方式解决:

  • 如果合约不可升级,并且存储位置只在构造函数中设置一次,则应声明该存储槽为 immutable

  • 否则,可以读取一次存储槽,缓存,并作为参数传递到当前函数和子函数中所需的位置

检查 Leverager::liquidatePosition 显示了一些快速简便的改进:

尝试简单缓存 feeReceipient 由于 "stack too deep" 错误而失败。在缓存存储时,在函数内使用局部变量更具gas效率,但如果由于 "stack too deep" 错误无法实现,那么可以:

  • 声明一个结构体 LiquidateContext 用于缓存清算函数的存储读取。
struct LiquidateContext {
    address feeRecipient;
    address swapRouter;
}
  • 通过将 IVault(up.vault).twapPrice() 的结果直接传递给 _calculateTokenValues,消除一个只读取一次的局部变量

  • 在清算函数中声明一个新的局部变量 LiquidateContext memory ctx,以存储缓存的存储读取

1661887 的提交中可以看到这些更改和结果;一个测试的 gas 成本下降,但其他两个则上升,尽管这只是个开始,我们正在为进一步优化打下基础。

同样,有意义的是将 Position memory pos 变量合并到我们的 LiquidateContext 结构中,并创建一个新的 _getLiquidateContext 内部函数来返回该结构:

struct LiquidateContext {
    // 从 positions[liqParams.id] 读取
    Position pos;

    address feeRecipient;
    address swapRouter;
}

function _getLiquidateContext(uint256 _id) internal view returns(LiquidateContext memory ctx) {
    ctx.pos = positions[_id];
}

/// @notice 清算某个杠杆头寸。
/// @dev 有关所有 LiquidateParams 参数的注释,请查看 ILeverager 合约
/// @dev 不支持部分清算
/// @dev 首先收取费用,以便正确计算头寸是否真的可清算
function liquidatePosition(LiquidateParams calldata liqParams) external collectFees(liqParams.id) nonReentrant {
    // 从存储读取所有所需内容一次
    LiquidateContext memory ctx = _getLiquidateContext(liqParams.id);

collectFees 修饰符读取相同存储槽

接下来检查修饰符,以查看它们是否重新读取相同的存储槽;开发人员通常会编写与函数体读取相同存储槽的修饰符,从而导致代码效率低下。在这种情况下,只有一个修饰符是有趣的 collectFees,它再次将整个 Position 复制到内存中,导致10个相同的存储读取:

modifier collectFees(uint256 _id) {
    // @gas 10个相同的存储读取,因为 `Position` 已经在 `liquidatePosition` 函数内存中读取过
    Position memory up = positions[_id];
    address strat = IVault(up.vault).strategy();
    IStrategy(strat).collectFees();
    _;
}

由于 collectFees 修饰符只由 liquidatePosition 函数调用,因此我们可以简单地 内联 它以节省10个存储读取:

function liquidatePosition(LiquidateParams calldata liqParams) external nonReentrant {
    // 从存储读取所有所需内容一次
    LiquidateContext memory ctx = _getLiquidateContext(liqParams.id);

    // 必须先收取费用,然后检验一个头寸是否可清算
    IStrategy(IVault(ctx.pos.vault).strategy()).collectFees();

通过这一更改,所有三个测试的gas效率均高于原始代码:

$ more snapshots/LeveragerTest.json
{
  "lendingPoolLiquidation": "574354",
  "liquidateWithSwap": "616824",
  "liquidationNoSwap": "438617"
}
$ more snapshots/LeveragerTest.original.json
{
  "lendingPoolLiquidation": "575367",
  "liquidateWithSwap": "618080",
  "liquidationNoSwap": "439622"
}

isLiquidatable 函数读取相同存储槽

完成修饰符后,开始检查函数体,查找子函数。第一个遇到的函数是 isLiquidatable;查看其实现,发现它也会再次将整个 Position 从存储中复制到内存,从而再次导致10个相同的存储读取:

function isLiquidatable(uint256 _id) public view returns (bool liquidatable) {
    // 当由 `liquidatePosition` 调用时,又进行了10个相同的存储读取
    Position memory pos = positions[_id];

在原始代码中,对于每个清算交易,Position 被复制到内存中三次,共进行了30个相同的存储读取!通过引入一个 internal 辅助函数 _isLiquidatable 来解决此问题:

  • 输入参数为缓存的 LiquidateContext memory ctx

  • 可以由 liquidatePositionisLiquidatable 调用

更改见提交 (143cec6, e960acf):

function liquidatePosition(LiquidateParams calldata liqParams) external nonReentrant {
    // 从存储读取所有所需内容一次
    LiquidateContext memory ctx = _getLiquidateContext(liqParams.id);

    // 必须先收取费用,然后检验一个头寸是否可清算
    IStrategy(IVault(ctx.pos.vault).strategy()).collectFees();

    require(_isLiquidateable(ctx), "isnt liquidateable");
}

function isLiquidateable(uint256 _id) public view returns (bool liquidateable) {
    liquidateable = _isLiquidateable(_getLiquidateContext(_id));
}

// gas: 内部辅助函数可以在清算期间节省读取 positions[_id] 多次
function _isLiquidateable(LiquidateContext memory ctx) internal view returns (bool liquidateable) {
    VaultParams memory vp = vaultParams[ctx.pos.vault];

    uint256 vaultSupply = IVault(ctx.pos.vault).totalSupply();

    // 假设价格为X,当池区价格恰好为 X 时,LP 头寸的最低值。
    // 任何价格变动实际上都会高估这个头寸。
    // 因此,攻击者无法通过交换使头寸变得可清算。
    (uint256 vaultBal0, uint256 vaultBal1) = IVault(ctx.pos.vault).balances();
    uint256 userBal0 = ctx.pos.shares * vaultBal0 / vaultSupply;
    uint256 userBal1 = ctx.pos.shares * vaultBal1 / vaultSupply;
    uint256 price = IVault(ctx.pos.vault).twapPrice();

    uint256 totalValueUSD = _calculateTokenValues(ctx.pos.token0, ctx.pos.token1, userBal0, userBal1, price);
    uint256 bPrice = IPriceFeed(pricefeed).getPrice(ctx.pos.denomination);
    uint256 totalDenom = totalValueUSD * (10 ** ERC20(ctx.pos.denomination).decimals()) / bPrice;

    uint256 bIndex = ILendingPool(lendingPool).getCurrentBorrowingIndex(ctx.pos.denomination);
    uint256 owedAmount = ctx.pos.borrowedAmount * bIndex / ctx.pos.borrowedIndex;

    /// 这里我们进行计算,确定如果我们拥有相同的借款金额,但以最大杠杆来进行,所需的抵押品。
    /// 查看文档了解为什么具体说明。
    uint256 base = owedAmount * 1e18 / (vp.maxTimesLeverage - 1e18);
    base = base < ctx.pos.initCollateralValue ? base : ctx.pos.initCollateralValue;

    if (owedAmount > totalDenom || totalDenom - owedAmount < vp.minCollateralPct * base / 1e18) return true;
}

这进一步带来了 gas 节省:

- [lendingPoolLiquidation] 574354 → 572916
- [liquidateWithSwap] 616824 → 615384
- [liquidationNoSwap] 438617 → 437178

_isLiquidatable 函数复制所有 VaultParams

接下来看 _isLiquidatable 函数,它:

  • 从存储中复制 VaultParams 到内存

  • 仅使用 VaultParams 中的 maxTimesLeverageminCollateralPct

因为 VaultParams 定义为:

struct VaultParams {
    bool leverageEnabled;
    uint256 maxUsdLeverage;
    uint256 maxTimesLeverage;
    uint256 minCollateralPct;
    uint256 maxCumulativeBorrowedUSD;
    uint256 currBorrowedUSD;
}

这意味着 _isLiquidatable 每次从存储中复制 VaultParams 都会多进行4个不必要的存储读取!通过以下方式修复此问题:

  • 扩展 LiquidateContext 结构以包含附加字段

  • _getLiquidateContext 中添加代码以缓存这些字段

  • 更改 _isLiquidatable 以使用缓存的字段

// 缓存存储读取到内存
struct LiquidateContext {
    // 从 positions[liqParams.id] 读取
    Position pos;

    // 从 vaultParams[pos.vault] 读取
    uint256 maxTimesLeverage;
    uint256 minCollateralPct;

    address feeRecipient;
    address swapRouter;
}

function _getLiquidateContext(uint256 _id) internal view returns(LiquidateContext memory ctx) {
    ctx.pos = positions[_id];

    VaultParams storage vpRef = vaultParams[ctx.pos.vault];
    (ctx.maxTimesLeverage, ctx.minCollateralPct)
        = (vpRef.maxTimesLeverage, vpRef.minCollateralPct);
}

function _isLiquidateable(LiquidateContext memory ctx) internal view returns (bool liquidateable) {
    /* 省略 */
    uint256 base = owedAmount * 1e18 / (ctx.maxTimesLeverage - 1e18);
    base = base < ctx.pos.initCollateralValue ? base : ctx.pos.initCollateralValue;

    if (owedAmount > totalDenom || totalDenom - owedAmount < ctx.minCollateralPct * base / 1e18) return true;
}

最终带来了进一步的 gas 节省:

- [lendingPoolLiquidation] 572916 → 572396
- [liquidateWithSwap] 615384 → 614864
- [liquidationNoSwap] 437178 → 436658

pricefeedliquidatePosition_isLiquidatable 中的相同读取

开发人员常犯的一个错误是在父函数调用的子函数中重新读取存储中的相同值;在本案例中:

  • liquidatePosition 调用 _isLiquidatable,而后者从存储中读取 pricefeed 到内存中

  • liquidatePosition 本身再次读取 pricefeed,导致重复存储读取

通过 修复

  • priceFeed 添加到 struct LiquidateContext

  • getLiquidateContext 内部读取一次

  • _isLiquidatableliquidatePosition 中从缓存副本读取

这再次减少了整体的 gas 成本:

- [lendingPoolLiquidation] 572396 → 572345
- [liquidateWithSwap] 614864 → 614813
- [liquidationNoSwap] 436658 → 436608

pricefeed_calculateTokenValues 中相同读取

继续检查 _isLiquidatable 子函数,发现它:

  • 调用 _calculateTokenValues,而该函数也被主函数 liquidatePosition 调用

  • _calculateTokenValues 最多从存储读取相同的 pricefeed 值4次:

if (IPriceFeed(pricefeed).hasPriceFeed(token0)) {
    chPrice0 = IPriceFeed(pricefeed).getPrice(token0);
    usdValue += amount0 * chPrice0 / decimals0;
}
if (IPriceFeed(pricefeed).hasPriceFeed(token1)) {
    chPrice1 = IPriceFeed(pricefeed).getPrice(token1);
    usdValue += amount1 * chPrice1 / decimals1;
}

这可以通过将 _calculateTokenValues 修改为接受作为输入的缓存 pricefeed简单修复,而获得更大的 gas 节省:

- [lendingPoolLiquidation] 572345 → 571293
- [liquidateWithSwap] 614813 → 613761
- [liquidationNoSwap] 436608 → 435556

Leverager 进一步优化

可以通过以下方式进行进一步但可能不安全的 gas 优化:

  • 缓存外部调用结果,例如 ILendingPool(lendingPool).getCurrentBorrowingIndex(ctx.pos.denomination)

  • 使用此来缓存计算得出的 owedAmount

理想情况下,若读取相同的值多次,如果是从存储(或从外部调用接收)中读取相同的值,则应缓存重复的计算和外部调用,但如果打算使后来的重复计算和外部调用返回不同的值,则这样做将不安全。缓存仅在多次读取相同值时才安全的优化。

对其他 Leverager 函数所做的其他安全 gas 优化包括:

Leverager 高审计结果 - feeRecipient 从未设置

在进行非可升级合约的 gas 优化时,查看存储槽是否可以被设为 immutable 是一个启发式方法。检查所有 Leverager 存储槽时,我发现 feeRecipient 并未在任何地方设置,这意味着:

  • 所有费用将发送到零地址,令允许这种情况的代币受到影响,或者

  • 清算将失败,对于那些在将代币发送到零地址时返回的代币

Strategy 高审计结果 - collectFees中的不正确定上限

在查找相同的存储读取以缓存时,我注意到这个代码块,其中对 collectPositionFees 的第三次调用再次从存储中读取 mainPosition.tickUpper,而这可能不该这样:

if (mainPosition.liquidity != 0) collectPositionFees(mainPosition.tickLower, mainPosition.tickUpper);
if (secondaryPosition.liquidity != 0) {
    collectPositionFees(secondaryPosition.tickLower, secondaryPosition.tickUpper);
}
if (ongoingVestingPosition) {
    // @audit `mainPosition.tickUpper` 可能不打算再次从
    // 存储中读取,应该是 `vestPosition.tickUpper`
    collectPositionFees(vestPosition.tickLower, mainPosition.tickUpper);
}

其影响是 Strategy::collectFees 可能会回退或未能收集正确的费用,因为它传递了错误的上限。

LendingPool Gas 优化

LendingPool 合约类似的 gas 优化也可以进行:

Strategy Gas 优化

Strategy 合约同样能够从以下 gas 优化中受益:

Vault Gas 优化

Vault 合约进行了若干小的 gas 优化:

内部库 Gas 优化

对执行存储引用的功能的内部库同样受益于 gas 优化:

临时Gas优化结果

运行 forge snapshot --diff --fork-url ETH_RPC_URL --fork-block-number 20956198 以获取 “测试函数快照” 的 gas 输出:

test_canDepositAtAnyPrice(uint256,uint256,uint256) (gas: -9887 (-1.166%))
test_canDepositAtAnyPrice(uint256,uint256,uint256) (gas: -9931 (-1.170%))
test_compound() (gas: -15758 (-1.207%))
test_compound() (gas: -15758 (-1.207%))
test_addVestingPosition() (gas: -19229 (-1.217%))
test_addVestingPosition() (gas: -19229 (-1.217%))
test_fuzzDepositAndWithdraw(uint256,uint256) (gas: -10083 (-1.376%))
test_fuzzDepositAndWithdraw(uint256,uint256) (gas: -10086 (-1.376%))
test_Gas_LendingPool() (gas: -40343 (-1.465%))
test_fuzzRebalance(uint256,uint256) (gas: -11360 (-1.478%))
test_fuzzRebalance(uint256,uint256) (gas: -11361 (-1.478%))
test_cantOpenPositionDueToVolatileVault() (gas: -14377 (-1.562%))
test_fuzzDepositWithdrawRemainRatio(uint256) (gas: -19643 (-1.634%))
test_fuzzDepositWithdrawRemainRatio(uint256) (gas: -19660 (-1.636%))
testDeposit() (gas: -16235 (-1.735%))
testDeposit() (gas: -16236 (-1.735%))
test_LendingPoolRepaymentFromBorrower() (gas: -30923 (-1.738%))
test_LendingPoolRepaymentAndClaim() (gas: -30923 (-1.802%))
test_borrowInThirdToken() (gas: -62728 (-2.585%))
test_partialWithdraw() (gas: -67099 (-2.880%))
test_vaultLimitsWork2() (gas: -55670 (-2.916%))
test_fuzzWithdraw(uint256) (gas: -10889 (-2.960%))
test_fuzzWithdraw(uint256) (gas: -10893 (-2.961%))
test_vaultLimitsWork1() (gas: -61307 (-3.212%))
test_Gas_liquidationNoSwap() (gas: -84678 (-3.814%))
test_Gas_liquidationWithSwap() (gas: -85708 (-3.840%))
test_cantOpenPositionWhenReserveInactive() (gas: -68329 (-4.091%))
test_lendingRate() (gas: -70875 (-4.276%))
test_permissionedFunctions() (gas: -108575 (-4.305%))
test_borrowToken1() (gas: -69476 (-4.594%))
test_cantOpenDueToPoolPriceOff() (gas: 0 (NaN%))
整体 gas 变更:-1077249 (-2.503%)

到目前为止,在整个测试套件中,我们的 gas 成本降低了 1.166% → 4.594%,整体 gas 减少了 2.503%,节省了 1,077,249 gas,主要通过重构现有的 Solidity 代码以减少相同存储读取的数量。

外部库 Gas 优化

该协议使用了来自 OpenZeppelin 的多个外部库。通过更改合约以使用 Solady 中类似的功能,可以实现更多的 gas 节省。首先使用以下方法安装 Solady:

  • forge install Vectorized/solady

  • "@solady/=lib/solady/src/", 添加到 foundry.toml 重映射

接下来,系统地将协议合约更改为使用 Solady 库而不是 OpenZeppelin:

使用优化后的外部库,运行 forge snapshot --diff --fork-url ETH_RPC_URL --fork-block-number 20956198 以刷新 “测试函数快照” 的 gas 输出:

test_compound() (gas: -20293 (-1.555%))
test_compound() (gas: -20293 (-1.555%))
test_canDepositAtAnyPrice(uint256,uint256,uint256) (gas: -14501 (-1.711%))
test_addVestingPosition() (gas: -28079 (-1.777%))
test_addVestingPosition() (gas: -28080 (-1.777%))
test_canDepositAtAnyPrice(uint256,uint256,uint256) (gas: -15237 (-1.796%))
test_fuzzRebalance(uint256,uint256) (gas: -15522 (-2.019%))
test_fuzzRebalance(uint256,uint256) (gas: -15522 (-2.019%))
test_fuzzDepositAndWithdraw(uint256,uint256) (gas: -15992 (-2.183%))
test_fuzzDepositAndWithdraw(uint256,uint256) (gas: -15994 (-2.183%))
test_fuzzDepositWithdrawRemainRatio(uint256) (gas: -27003 (-2.247%))
test_fuzzDepositWithdrawRemainRatio(uint256) (gas: -27004 (-2.247%))
testDeposit() (gas: -21996 (-2.351%))
testDeposit() (gas: -21996 (-2.351%))
test_cantOpenPositionDueToVolatileVault() (gas: -27920 (-3.034%))
test_Gas_LendingPool() (gas: -85556 (-3.107%))
test_fuzzWithdraw(uint256) (gas: -12440 (-3.382%))
test_fuzzWithdraw(uint256) (gas: -12444 (-3.382%))
test_permissionedFunctions() (gas: -94015 (-3.728%))
test_LendingPoolRepaymentFromBorrower() (gas: -78629 (-4.418%))
test_LendingPoolRepaymentAndClaim() (gas: -78069 (-4.548%))
test_partialWithdraw() (gas: -125069 (-5.367%))
test_borrowInThirdToken() (gas: -135182 (-5.570%))
test_lendingRate() (gas: -92864 (-5.602%))
test_vaultLimitsWork2() (gas: -110189 (-5.773%))
test_borrowToken1() (gas: -89333 (-5.907%))
test_cantOpenPositionWhenReserveInactive() (gas: -98835 (-5.917%))
test_vaultLimitsWork1() (gas: -115929 (-6.073%))
test_Gas_liquidationNoSwap() (gas: -136030 (-6.126%))
test_Gas_liquidationWithSwap() (gas: -138334 (-6.198%))
test_cantOpenDueToPoolPriceOff() (gas: 0 (NaN%))
Overall gas change: -1718350 (-3.992%)

优化外部库增加了:

  • 最小Gas节省从 1.166% 提升到 1.555%

  • 最大Gas节省从 4.594% 提升到 6.198%

  • 总体Gas节省从 2.503% 提升到 3.992%,节省了 1,718,350 的 gas,而不仅仅是 1,077,249,这真是一个 37% 的提升!

启用优化器

通过在 foundry.toml 中添加以下内容,我们还可以做得更好,以 启用优化器

optimizer = true
optimizer_runs = 1_000_000

现在运行 forge snapshot --diff --fork-url ETH_RPC_URL --fork-block-number 20956198 刷新“测试函数快照”的 gas 输出为:

test_fuzzWithdraw(uint256) (gas: -29626 (-8.053%))
test_fuzzWithdraw(uint256) (gas: -29628 (-8.054%))
test_compound() (gas: -139490 (-10.689%))
test_compound() (gas: -139525 (-10.691%))
test_fuzzDepositWithdrawRemainRatio(uint256) (gas: -143294 (-11.923%))
test_fuzzDepositWithdrawRemainRatio(uint256) (gas: -143340 (-11.927%))
test_addVestingPosition() (gas: -201759 (-12.766%))
test_addVestingPosition() (gas: -201764 (-12.767%))
test_Gas_LendingPool() (gas: -390653 (-14.188%))
test_canDepositAtAnyPrice(uint256,uint256,uint256) (gas: -120991 (-14.260%))
test_canDepositAtAnyPrice(uint256,uint256,uint256) (gas: -120980 (-14.271%))
test_borrowInThirdToken() (gas: -354141 (-14.593%))
test_partialWithdraw() (gas: -341000 (-14.634%))
testDeposit() (gas: -137266 (-14.672%))
testDeposit() (gas: -137286 (-14.673%))
test_LendingPoolRepaymentAndClaim() (gas: -261501 (-15.235%))
test_cantOpenPositionDueToVolatileVault() (gas: -141161 (-15.338%))
test_LendingPoolRepaymentFromBorrower() (gas: -273610 (-15.374%))
test_fuzzRebalance(uint256,uint256) (gas: -119467 (-15.540%))
test_fuzzRebalance(uint256,uint256) (gas: -119520 (-15.546%))
test_Gas_liquidationWithSwap() (gas: -351131 (-15.731%))
test_vaultLimitsWork2() (gas: -300941 (-15.765%))
test_vaultLimitsWork1() (gas: -305623 (-16.010%))
test_fuzzDepositAndWithdraw(uint256,uint256) (gas: -120873 (-16.496%))
test_fuzzDepositAndWithdraw(uint256,uint256) (gas: -120924 (-16.502%))
test_Gas_liquidationNoSwap() (gas: -367226 (-16.539%))
test_lendingRate() (gas: -286293 (-17.271%))
test_borrowToken1() (gas: -265396 (-17.548%))
test_cantOpenPositionWhenReserveInactive() (gas: -293424 (-17.566%))
test_permissionedFunctions() (gas: -684835 (-27.154%))
test_cantOpenDueToPoolPriceOff() (gas: 0 (NaN%))
Overall gas change: -6642668 (-15.434%)

启用优化器在各个方面提供了重大的 gas 改进,增加了:

  • 最小Gas节省从 1.555% 提升到 8.053%

  • 最大Gas节省从 6.198% 提升到 27.154%

  • 总体Gas节省从 3.992% 提升到 15.434%,节省了 6,642,668 的 gas,而不仅仅是 1,718,350,导致令人惊叹的 3.86 倍的提升!

最后,让我们通过运行 forge test --gas-snapshot-check=true --match-test test_Gas --fork-url ETH_RPC_URL --fork-block-number 20956198 获取在关键 Leverager::liquidatePosition 函数周围放置的“Gas部分快照”:

- [lendingPoolLiquidation] 575367 → 484711 (-15.75%)
- [liquidateWithSwap] 618080 → 536423 (-13.211%)
- [liquidationNoSwap] 439622 → 365286 (-16.909%)

我们已将调用 Leverager::liquidatePosition 的 gas 成本减少了 13.211% 到 16.909%,这与之前观察到的整体Gas减少 15.434% 非常吻合。

想与我一起进行你的协议的 Gas 审计吗?随时联系我

额外资源

  • 原文链接: dacian.me/the-yieldoor-g...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Dacian
Dacian
in your storage