这篇文章通过一个实际案例研究了如何通过重构优化 Solidity 代码来实现气体节省,展示了在一个杠杆收益农场协议 Yieldoor 中,通过改进核心函数 Leverager::liquidatePosition
实现了 15.43% 的气体节约。作者详细描述了测量Gas成本的方法和重构过程,包括减少冗余存储读取、使用结构体缓存、以及启用优化器以提高代码效率。
Decorated 审计师 deadrosesxyz 最近戴上了开发者的帽子,创建了一个杠杆收益农场协议 Yieldoor,其代码提供了大量的gas优化机会。许多文章列出了常见的gas优化技术,但这个真实案例研究展示了通过逐步的思维过程重构,优化核心协议功能而实现的15.43%的整体gas节省。
我们将使用公开可用的竞赛 源代码,并先专注于最有趣、最复杂和关键的清算函数 Leverager::liquidatePosition
。
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();
通过运行 forge snapshot --fork-url ETH_RPC_URL --fork-block-number 20956198
获取初始的 “测试函数快照”,生成一个新的文件 .gas-snapshot
,记录所有单元测试和整体的 gas 成本(上述第二个选项)
通过运行 forge test --gas-snapshot-check=true --match-test test_Gas --fork-url ETH_RPC_URL --fork-block-number 20956198
获取初始的 “gas 区块快照”,生成文件 snapshots/LeveragerTest.json
,显示包装对 Leverager::liquidatePosition
的调用的手动快照的初始 gas 成本(上述第三个选项):
{
"lendingPoolLiquidation": "575367",
"liquidateWithSwap": "618080",
"liquidationNoSwap": "439622"
}
将这些文件备份到 .gas-snapshot.original
和 snapshots/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
显示了一些快速简便的改进:
feeRecipient
在 L328,329 中被读取了两次
swapRouter
在 L354,355,363,364 之间被读取了两到四次
尝试简单缓存 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
可以由 liquidatePosition
和 isLiquidatable
调用
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
中的 maxTimesLeverage
和 minCollateralPct
因为 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
pricefeed
在 liquidatePosition
和 _isLiquidatable
中的相同读取开发人员常犯的一个错误是在父函数调用的子函数中重新读取存储中的相同值;在本案例中:
liquidatePosition
调用 _isLiquidatable
,而后者从存储中读取 pricefeed
到内存中
liquidatePosition
本身再次读取 pricefeed
,导致重复存储读取
通过 修复 :
将 priceFeed
添加到 struct LiquidateContext
在 getLiquidateContext
内部读取一次
在 _isLiquidatable
和 liquidatePosition
中从缓存副本读取
这再次减少了整体的 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 优化也可以进行:
在 pullFunds
, pushFunds
和 getLeverageParams
中不复制整个 ReserveData
Strategy
Gas 优化Strategy
合约同样能够从以下 gas 优化中受益:
Vault
Gas 优化Vault
合约进行了若干小的 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 代码以减少相同存储读取的数量。
该协议使用了来自 OpenZeppelin 的多个外部库。通过更改合约以使用 Solady 中类似的功能,可以实现更多的 gas 节省。首先使用以下方法安装 Solady:
forge install Vectorized/solady
将 "@solady/=lib/solady/src/",
添加到 foundry.toml
重映射
接下来,系统地将协议合约更改为使用 Solady 库而不是 OpenZeppelin:
在 yToken
中使用 Solady 的 ERC20
, SafeTransferLib
和 ReentrancyGuardTransient
在 Strategy
中使用 Solady 的 SafeTransferLib
, Ownable
和 FixedPointMathLib
在 Leverager
中使用 Solady 的 ERC721
, Ownable
, SafeTransferLib
和 ReentrancyGuardTransient
在 LendingPool
中使用 Solady 的 Ownable
, SafeTransferLib
和 ReentrancyGuardTransient
使用优化后的外部库,运行 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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!