本文深入探讨了 EulerSwap 中 vault 的提款流程,EulerSwap 是一个先进的去中心化交易所,它将 Euler Finance 的借贷基础设施与 Uniswap v4 的 hook 架构相结合,为流动性提供者提供先进的资本效率、收益生成和风险管理工具。
EulerSwap 是一个前沿的去中心化交易所(DEX),它将 Euler Finance 的借贷基础设施与 Uniswap v4 的 hook 架构相结合,为流动性提供者(LP)提供高级的资本效率、收益生成和风险管理工具。
EulerSwap 的一个关键组件是它的 vault(金库)系统,该系统使用 Euler Vault Kit (EVK) 构建,并符合 ERC-4626 代币化金库标准。该系统允许 LP 将资产存入 vault,赚取交易费用和借贷收益,并将这些存款用作 just-in-time (JIT) 流动性供应的抵押品。
理解 vault 提款流程对于 LP、开发者和 DeFi 爱好者来说至关重要,这样才能有效地与这一创新协议互动。
EulerSwap 的 vault 提款流程是一个复杂而安全的机制,它确保 LP 可以赎回他们的资产,同时保持协议的流动性和稳定性。该流程涉及两个主要组件:Ethereum Vault Connector (EVC),它处理身份验证、授权和延迟状态检查;以及 Euler Vault Kit (EVK),它在 vault 内执行提款逻辑。这种职责分离使得 EulerSwap 可以将验证职责委托给 EVC,从而确保所有系统状态在执行后保持有效。
当 LP 通过 EulerSwap 发起提款请求时,提款流程开始,这会触发一系列智能合约交互。这些交互包括通过 EVC 进行身份验证、在 vault 中执行,以及最终状态检查以验证帐户和 vault 的健康状况。该流程旨在优先考虑安全性,利用广泛的审计和一个 50 万美元的夺旗竞赛来对系统进行压力测试。
以下是理解 EulerSwap 中 vault 提款为何重要的原因:
下面,我们将深入研究每个步骤的技术细节,并提供代码片段和实践见解。
EVC 是 EulerSwap 架构中的中央控制器,负责对调用者进行身份验证、管理执行上下文以及将状态检查推迟到操作完成后。当 LP 通过 EulerSwap 发起提款时,该流程首先调用 EVC 的 call()
函数,该函数由第 151 行的 FundsLib::withdrawAssets
函数触发:
IEVC(evc).call(vault, p.eulerAccount, 0, abi.encodeCall(IERC4626.withdraw, (avail, to, p.eulerAccount)));
call()
函数执行从 EthereumVaultConnector::call
开始,它设置了一个延迟检查框架,以确保操作在没有立即验证的情况下进行,从而在保持安全性的同时提供灵活性:
function call(
address targetContract,
address onBehalfOfAccount,
uint256 value,
bytes calldata data
) public payable virtual nonReentrantChecksAndControlCollateral returns (bytes memory result) {
EC contextCache = executionContext;
executionContext = contextCache.setChecksDeferred();
bool success;
(success, result) = callWithAuthenticationInternal(targetContract, onBehalfOfAccount, value, data);
if (!success) revertBytes(result);
restoreExecutionContext(contextCache);
}
contextCache.setChecksDeferred()
调用将帐户健康状况检查(例如,抵押率)和 vault 状态检查(例如,供需上限)推迟到执行之后,从而降低 gas 成本并启用复杂的操作。contextCache
存储调用前的状态,允许 EVC 在执行后恢复它。nonReentrantChecksAndControlCollateral
修饰符可防止重入攻击,这是一项关键的安全功能。callWithAuthenticationInternal
进行身份验证然后,EVC 调用 callWithAuthenticationInternal
以验证调用者的身份并路由请求:
function callWithAuthenticationInternal(
address targetContract,
address onBehalfOfAccount,
uint256 value,
bytes calldata data
) internal virtual returns (bool success, bytes memory result) {
if (targetContract == address(this)) {
if (onBehalfOfAccount != address(0)) revert EVC_InvalidAddress();
if (value != 0) revert EVC_InvalidValue();
(success, result) = address(this).delegatecall(data);
} else {
if (targetContract != msg.sender) {
authenticateCaller({account: onBehalfOfAccount, allowOperator: true, checkLockdownMode: true});
}
(success, result) = callWithContextInternal(targetContract, onBehalfOfAccount, value, data);
}
}
targetContract
: vault 地址。onBehalfOfAccount
: LP 的帐户 ( p.eulerAccount
)。value
: 设置为 0(无 ETH 转账)。data
: 编码调用 IERC4626.withdraw(avail, to, p.eulerAccount)
。targetContract
(vault) 不是 EVC 或 msg.sender
(EulerSwap),因此该函数继续调用 authenticateCaller
以验证权限。authenticateCaller
函数确保调用者被授权代表该帐户行事:
function authenticateCaller(
address account,
bool allowOperator,
bool checkLockdownMode
) internal virtual returns (address) {
bytes19 addressPrefix = getAddressPrefixInternal(account);
address owner = ownerLookup[addressPrefix].owner;
bool lockdownMode = ownerLookup[addressPrefix].isLockdownMode;
address msgSender = _msgSender();
bool authenticated = false;
if (haveCommonOwnerInternal(account, msgSender)) {
if (owner == address(0)) {
ownerLookup[addressPrefix].owner = msgSender;
emit OwnerRegistered(addressPrefix, msgSender);
authenticated = true;
} else if (owner == msgSender) {
authenticated = true;
}
}
if (!authenticated && allowOperator && isAccountOperatorAuthorizedInternal(account, msgSender)) {
authenticated = true;
}
if (authenticated && owner != account && account.code.length != 0) {
authenticated = false;
}
if (!authenticated) revert EVC_NotAuthorized();
if (checkLockdownMode && lockdownMode) revert EVC_LockdownMode();
return msgSender;
}
p.eulerAccount
的前 19 个字节标识帐户的所有者,每个所有者最多支持 256 个子帐户。msgSender
(EulerSwap) 是否与 account
(p.eulerAccount) 共享一个共同的所有者。由于 EulerSwap 是一个单独的合约,因此此检查失败。isAccountOperatorAuthorizedInternal
返回 true
,并将 authenticated
设置为 true
。身份验证后,callWithContextInternal
设置执行上下文并调用 vault:
function callWithContextInternal(
address targetContract,
address onBehalfOfAccount,
uint256 value,
bytes calldata data
) internal virtual returns (bool success, bytes memory result) {
if (value == type(uint256).max) {
value = address(this).balance;
} else if (value > address(this).balance) {
revert EVC_InvalidValue();
}
EC contextCache = executionContext;
address msgSender = _msgSender();
if (
haveCommonOwnerInternal(onBehalfOfAccount, msgSender) ||
targetContract == msg.sender ||
targetContract == address(this) ||
contextCache.isControlCollateralInProgress()
) {
executionContext = contextCache.setOnBehalfOfAccount(onBehalfOfAccount).clearOperatorAuthenticated();
} else {
executionContext = contextCache.setOnBehalfOfAccount(onBehalfOfAccount).setOperatorAuthenticated();
}
emit CallWithContext(
msgSender, getAddressPrefixInternal(onBehalfOfAccount), onBehalfOfAccount, targetContract, bytes4(data)
);
(success, result) = targetContract.call{value: value}(data);
executionContext = contextCache;
}
onBehalfOfAccount
设置为 p.eulerAccount
,并且由于 if
语句中的任何条件(共同所有者、相同发送者等)都不为真,因此操作员被标记为已通过身份验证。vault.withdraw(avail, to, p.eulerAccount)
。提款逻辑在遵循 ERC-4626 标准的 Euler vault 中执行。入口点是 EVault::withdraw
函数,该函数通过修饰符委托给模块:
function withdraw(uint256 amount, address receiver, address owner)
public
virtual
override
callThroughEVC
use(MODULE_VAULT)
returns (uint256) {}
callThroughEVC
修饰符callThroughEVC
修饰符确保所有调用都通过 EVC 路由,以实现一致的状态管理:
modifier callThroughEVC() {
if (msg.sender == address(evc)) {
_;
} else {
callThroughEVCInternal();
}
}
由于 msg.sender
是 EVC(来自之前的调用),因此该修饰符允许执行继续执行 vault 的逻辑。
use(MODULE_VAULT)
修饰符use
修饰符将调用委托给包含核心提款逻辑的 MODULE_VAULT
模块:
modifier use(address module) {
_;
delegateToModule(module);
}
function delegateToModule(address module) private {
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), module, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
这会将执行路由到 Vault::withdraw
:
function withdraw(uint256 amount, address receiver, address owner)
public
virtual
nonReentrant
returns (uint256) {
(VaultCache memory vaultCache, address account) = initOperation(OP_WITHDRAW, owner);
Assets assets = amount.toAssets();
if (assets.isZero()) return 0;
Shares shares = assets.toSharesUp(vaultCache);
finalizeWithdraw(vaultCache, assets, shares, account, receiver, owner);
return shares.toUint();
}
initOperation
函数通过更新 vault 的状态和排队状态检查来设置提款。amount
将转换为 Assets
(一种用于精度的包装器类型)。toSharesUp
函数计算要销毁的份额数,向上舍入以确保偿付能力。finalizeWithdraw
函数销毁份额并将资产转移到接收者。initOperation
细节initOperation
函数执行关键设置:
function initOperation(uint32 operation, address accountToCheck)
internal
virtual
returns (VaultCache memory vaultCache, address account)
{
vaultCache = updateVault();
account = EVCAuthenticateDeferred(CONTROLLER_NEUTRAL_OPS & operation == 0);
callHook(vaultCache.hookedOps, operation, account);
EVCRequireStatusChecks(accountToCheck == CHECKACCOUNT_CALLER ? account : accountToCheck);
if (
!vaultCache.snapshotInitialized
&& !(vaultCache.supplyCap == type(uint256).max && vaultCache.borrowCap == type(uint256).max)
) {
vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = true;
snapshot.set(vaultCache.cash, vaultCache.totalBorrows.toAssetsUp());
}
}
updateVault
累积利息并更新总供应量和借款余额。EVCAuthenticateDeferred
确认调用来自 EVC 并跳过提款的控制器检查(因为 OP_WITHDRAW
在 CONTROLLER_NEUTRAL_OPS
中)。EVCRequireStatusChecks
会将帐户和 vault 状态检查排队以进行执行后验证。EulerSwap 优先使用闲置资产(vault 中未分配的资金)进行提款,以最大程度地减少中断:
uint256 vaultBalance = token.balanceOf(address(this));
if (withdrawAmount <= vaultBalance) {
processWithdrawal(withdrawAmount);
}
如果闲置资产不足,该协议会处理提款队列,即策略的有序列表(外部收益生成合约):
for (uint256 i = 0; i < withdrawalQueue.length; i++) {
address strategy = withdrawalQueue[i];
uint256 available = strategy.availableFunds();
uint256 toWithdraw = Math.min(available, remainingAmount);
strategy.withdraw(toWithdraw);
remainingAmount -= toWithdraw;
if (remainingAmount == 0) break;
}
如果策略缺乏流动性,可能会强制启动提款,可能会造成损失:
uint256 loss = strategy.forceWithdraw(amountNeeded);
if (loss > 0) {
totalLoss += loss;
emit LossIncurred(strategy, loss);
}
require(totalLoss <= maxLoss, "Loss exceeds acceptable limit");
finalizeWithdraw
函数会销毁计算出的份额并将资产转移给接收者:
burnShares(owner, shares);
token.safeTransfer(receiver, withdrawAmount);
在 vault 执行提款后,控制权返回到 EVC 的 call
函数,该函数调用 restoreExecutionContext
以执行延迟状态检查:
function restoreExecutionContext(EC contextCache) internal virtual {
if (!contextCache.areChecksDeferred()) {
executionContext = contextCache.setChecksInProgress().setOnBehalfOfAccount(address(0));
checkStatusAll(SetType.Account);
checkStatusAll(SetType.Vault);
}
executionContext = contextCache;
}
checkStatusAll(SetType.Account)
调用验证帐户的健康状况:
function checkAccountStatusInternal(address account)
internal
virtual
returns (bool isValid, bytes memory result) {
SetStorage storage accountControllersStorage = accountControllers[account];
uint256 numOfControllers = accountControllersStorage.numElements;
address controller = accountControllersStorage.firstElement;
if (numOfControllers == 0) return (true, "");
else if (numOfControllers > 1) return (false, abi.encodeWithSelector(EVC_ControllerViolation.selector));
bool success;
(success, result) = controller.staticcall(
abi.encodeCall(IVault.checkAccountStatus, (account, accountCollaterals[account].get()))
);
isValid = success && result.length == 32
&& abi.decode(result, (bytes32)) == bytes32(IVault.checkAccountStatus.selector);
}
checkAccountStatus
函数验证帐户的抵押品价值是否超过其负债,从而防止清算风险。checkStatusAll(SetType.Vault)
调用验证 vault 的状态:
function checkVaultStatusInternal(address vault)
internal
virtual
returns (bool isValid, bytes memory result) {
bool success;
(success, result) = vault.call(abi.encodeCall(IVault.checkVaultStatus, ()));
isValid = success && result.length == 32
&& abi.decode(result, (bytes32)) == bytes32(IVault.checkVaultStatus.selector);
}
这会调用 RiskManager::checkVaultStatus
:
function checkVaultStatus()
public
virtual
reentrantOK
onlyEVCChecks
returns (bytes4 magicValue) {
VaultCache memory vaultCache = updateVault();
uint256 newInterestRate = computeInterestRate(vaultCache);
if (vaultCache.snapshotInitialized) {
vaultStorage.snapshotInitialized = vaultCache.snapshotInitialized = false;
Assets snapshotCash = snapshot.cash;
Assets snapshotBorrows = snapshot.borrows;
uint256 prevBorrows = snapshotBorrows.toUint();
uint256 borrows = vaultCache.totalBorrows.toAssetsUp().toUint();
if (borrows > vaultCache.borrowCap && borrows > prevBorrows)
revert E_BorrowCapExceeded();
uint256 prevSupply = snapshotCash.toUint() + prevBorrows;
uint256 supply = vaultCache.cash.toUint() + vaultCache.totalBorrows.toAssetsDown().toUint();
if (supply > vaultCache.supplyCap && supply > prevSupply)
revert E_SupplyCapExceeded();
snapshot.reset();
}
magicValue = IEVCVault.checkVaultStatus.selector;
}
initOperation
的快照用于确保不超过供需上限,除非它们已被违反(在这种情况下,它们必须减少)。EulerSwap 中的 vault 提款流程是一个经过精心设计的序列,它平衡了效率、安全性和灵活性。EVC 验证和路由请求,EVK 执行提款同时管理流动性,执行后检查确保系统保持稳定。主要功能包括:
- 原文链接: medium.com/@ankitacode11...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!