Skip to main content

lifi 合约概览

从你提供的文件结构来看,这是一个基于EIP-2535(钻石标准)的智能合约项目。这个项目通过模块化设计,将不同的功能分散到多个独立的面(Facet)中,从而实现了功能的扩展和合约的升级。以下是对各个目录和文件的详细解释:

顶层文件

  • LiFiDiamond.solLiFiDiamondImmutable.sol:这两个文件很可能是主钻石合约,负责管理和路由所有的函数调用。LiFiDiamond.sol 可能是可变的版本,而 LiFiDiamondImmutable.sol 是不可变的版本。

Errors

  • GenericErrors.sol:定义了一些通用的错误信息,供整个项目使用。

Facets

这个目录包含了所有的面,每个面都实现了一组相关的功能:

  • AccessManagerFacet.sol:管理访问控制的功能。
  • AcrossFacet.sol, AcrossFacetPacked.sol:处理Across协议相关的功能。
  • AllBridgeFacet.sol:处理AllBridge协议相关的功能。
  • AmarokFacet.sol, AmarokFacetPacked.sol:处理Amarok协议相关的功能。
  • ArbitrumBridgeFacet.sol:处理Arbitrum桥接相关的功能。
  • CBridgeFacet.sol, CBridgeFacetPacked.sol:处理CBridge协议相关的功能。
  • CalldataVerificationFacet.sol:验证调用数据的功能。
  • CelerCircleBridgeFacet.sol:处理Celer Circle桥接相关的功能。
  • CelerIMFacetImmutable.sol, CelerIMFacetMutable.sol:处理Celer IM协议相关的功能,分别为不可变和可变版本。
  • CircleBridgeFacet.sol:处理Circle桥接相关的功能。
  • DexManagerFacet.sol:管理去中心化交易所(DEX)相关的功能。
  • DiamondCutFacet.sol:实现EIP-2535中的diamondCut功能,用于添加、移除或替换面。 //////////////////////////////////////////
  • DiamondLoupeFacet.sol:实现EIP-2535中的diamondLoupe功能,用于查询钻石合约的状态。
  • GenericSwapFacet.sol, GenericSwapFacetV3.sol:处理通用的交换功能。
  • GnosisBridgeFacet.sol, GnosisBridgeL2Facet.sol:处理Gnosis桥接相关的功能。
  • HopFacet.sol, HopFacetOptimized.sol, HopFacetPacked.sol:处理Hop协议相关的功能。
  • HyphenFacet.sol:处理Hyphen协议相关的功能。
  • LIFuelFacet.sol:处理LIFuel相关的功能。
  • MayanFacet.sol:处理Mayan协议相关的功能。
  • NonStandardSelectorsRegistryFacet.sol:处理非标准选择器的注册功能。
  • OmniBridgeFacet.sol:处理OmniBridge相关的功能。
  • OptimismBridgeFacet.sol:处理Optimism桥接相关的功能。
  • OwnershipFacet.sol:处理合约所有权管理的功能。
  • PeripheryRegistryFacet.sol:处理外围组件注册的功能。
  • PolygonBridgeFacet.sol:处理Polygon桥接相关的功能。
  • SquidFacet.sol:处理Squid协议相关的功能。
  • StandardizedCallFacet.sol:处理标准化调用的功能。
  • StargateFacet.sol, StargateFacetV2.sol:处理Stargate协议相关的功能。
  • SymbiosisFacet.sol:处理Symbiosis协议相关的功能。
  • ThorSwapFacet.sol:处理ThorSwap协议相关的功能。
  • WithdrawFacet.sol:处理提现相关的功能。

Helpers

这个目录包含了一些辅助合约和库:

  • CelerIMFacetBase.sol:Celer IM协议的基础合约。
  • ExcessivelySafeCall.sol:处理安全调用的库。
  • ReentrancyGuard.sol:防止重入攻击的库。
  • SwapperV2.sol:处理交换功能的库。
  • TransferrableOwnership.sol:处理可转移所有权的库。
  • Validatable.sol:处理验证功能的库。

Interfaces

这个目录包含了所有的接口定义,这些接口定义了不同协议和组件的标准化方法:

  • IAcrossSpokePool.solIAllBridge.solICBridge.sol,等:定义了不同协议的接口。

Libraries

这个目录包含了一些通用的库:

  • LibAccess.sol:处理访问控制的库。
  • LibAllowList.sol:处理允许列表的库。
  • LibAsset.sol:处理资产相关的库。
  • LibBytes.sol:处理字节数组的库。
  • LibDiamond.sol:处理钻石标准相关的库。
  • LibSwap.sol:处理交换相关的库。
  • LibUtil.sol:处理实用功能的库。
  • OFTComposeMsgCodec.sol:处理消息编码的库。

Periphery

这个目录包含了一些外围组件:

  • ERC20Proxy.sol:处理ERC20代理的合约。
  • Executor.sol:处理执行功能的合约。
  • FeeCollector.sol:处理费用收集的合约。
  • GasRebateDistributor.sol:处理燃气费返还的合约。
  • LiFiDEXAggregator.sol:处理去中心化交易所聚合的合约。
  • LiFuelFeeCollector.sol:处理LIFuel费用收集的合约。
  • Receiver.solReceiverStargateV2.sol:处理接收功能的合约。
  • RelayerCelerIM.sol:处理Celer IM协议的中继合约。
  • ServiceFeeCollector.sol:处理服务费用收集的合约。
  • TokenWrapper.sol:处理代币包装的合约。

总结

这个项目通过EIP-2535(钻石标准)实现了一个模块化、可扩展和易升级的智能合约系统。每个面(Facet)都负责特定的功能,主钻石合约负责路由和管理这些功能。这样设计不仅解决了合约大小限制的问题,还提供了灵活的升级和扩展能力。

QA?

   address _init, bytes memory _calldata 我没看懂,增删改,怎么会用到这两个字段?

我看 struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
} 已经有增删改需要的字段了

你提到的 address _initbytes memory _calldatadiamondCut 函数中的作用并不是直接用于增删改函数选择器,而是用于在完成这些操作之后执行初始化代码。这两个参数提供了一种机制,允许在完成 facet 变更后立即执行一些初始化逻辑。

详细解析

FacetCut 结构体

首先,FacetCut 结构体定义了增删改操作所需的信息:

struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
  • facetAddress:facet 的地址。
  • action:指定操作类型(添加、替换或移除)。
  • functionSelectors:需要操作的函数选择器数组。

这部分确实包含了增删改所需的所有信息。

address _initbytes memory _calldata

diamondCut 函数中,这两个参数的作用是:

  1. _init:一个地址,指向一个包含初始化代码的合约。
  2. _calldata:初始化代码的数据。

代码解析

diamondCut 函数中,增删改操作完成后,会触发 DiamondCut 事件并调用 initializeDiamondCut 函数:

emit DiamondCut(_diamondCut, _init, _calldata);
initializeDiamondCut(_init, _calldata);

initializeDiamondCut 函数

这个函数通常会执行如下操作:

function initializeDiamondCut(address _init, bytes memory _calldata) internal {
if (_init == address(0)) {
return;
}
(bool success, bytes memory error) = _init.delegatecall(_calldata);
if (!success) {
if (error.length > 0) {
// Bubble up the error
assembly {
let error_size := mload(error)
revert(add(32, error), error_size)
}
} else {
revert InitializationFailed();
}
}
}
  • 检查 _init 地址:如果 _init 是零地址,则直接返回,不执行任何初始化。
  • delegatecall:使用 _init 地址和 _calldata 执行 delegatecall,这允许在当前合约的上下文中执行初始化代码。
  • 错误处理:如果 delegatecall 失败,则回退并抛出错误。

为什么需要初始化代码?

  1. 状态更新:在增删改函数选择器后,可能需要更新合约的状态。例如,设置某些变量或初始化某些数据结构。
  2. 逻辑初始化:有时,新的 facet 可能需要一些初始化逻辑来确保其正确运行。
  3. 灵活性:提供了一种灵活的机制,可以在不修改合约代码的情况下,动态执行初始化逻辑。

总结

  • 增删改操作:由 _diamondCut 参数中的 FacetCut 结构体定义。
  • 初始化代码_init_calldata 提供了一种机制,允许在增删改操作完成后,执行额外的初始化逻辑。

这种设计增强了钻石合约的灵活性,使其不仅可以动态管理函数选择器,还可以在变更后立即进行必要的初始化操作。

为什么发送到以太要单独出来,不在_data中

在 Solidity 中,发送以太币和传递数据是通过不同的机制实现的。这种设计有其历史和技术上的原因。让我们详细解释一下为什么发送的以太币(value)需要单独指定,而不是包含在数据(_data)中。

以太币和数据的分离

  1. 以太坊交易模型

    • 以太坊的交易模型本质上是基于账户的,每个交易可以包含以下几个主要部分:
      • 目标地址:接收交易的合约或账户地址。
      • 发送的以太币数量:交易中包含的以太币数量(value)。
      • 数据:交易中包含的调用数据(data)。
      • gas:交易执行所需的 gas 量。
      • 签名:验证交易发送者的签名。
    • 在这个模型中,发送的以太币(value)和调用数据(data)是独立的字段。
  2. 合约调用的设计

    • 在 Solidity 中,call 方法是一个低级函数调用,用于与另一个合约进行交互。它允许你指定发送的以太币数量和调用数据。
    • call 的语法设计为 { value: _value }(_data) 分开,是为了保持灵活性和清晰性。这样可以明确区分发送的以太币和调用数据,避免混淆。

技术原因

  1. 交易签名和验证

    • 以太坊的交易签名和验证机制需要明确知道每个字段的含义。如果将以太币数量包含在数据中,会使得签名和验证过程更加复杂。
    • 通过将 value 单独指定,交易的签名和验证过程可以保持简单和高效。
  2. 合约接口的一致性

    • 合约函数的调用数据是通过 ABI 编码生成的,通常包括函数选择器和参数。这个数据结构不包含发送的以太币数量。
    • 通过将 value 单独指定,合约接口可以保持一致性和通用性,而不需要为每个函数定义额外的参数来处理以太币。

示例

考虑以下示例,展示如何使用 call 方法发送以太币和调用数据:

pragma solidity ^0.8.0;

contract Target {
event Log(string message, uint value);

function foo() external payable {
emit Log("foo was called", msg.value);
}
}

contract Caller {
function callFoo(address payable target) public payable {
bytes memory data = abi.encodeWithSignature("foo()");
(bool success, bytes memory res) = target.call{ value: msg.value }(data);
require(success, "Call failed");
}
}

在这个示例中:

  • abi.encodeWithSignature("foo()"):将函数签名编码为调用数据。
  • target.call{ value: msg.value }(data):调用目标合约的 foo 函数,并发送调用者发送的以太币(msg.value)。

总结

将发送的以太币数量(value)和调用数据(_data)分开指定,是为了保持交易模型的清晰性和合约调用接口的一致性。这种设计使得交易的签名和验证过程更加简单,同时也保持了合约接口的通用性和灵活性。

alt text