本文介绍了 Diamond (EIP-2535) 协议,它是一种将智能合约的功能模块化并使其可升级的方法。Diamond 允许将合约逻辑分割成多个 facet,这些 facet 可以独立升级,从而突破了 EVM 的 24KB 代码大小限制,并提供了一种组织和管理大型智能合约代码库的有效方式。文章还提供了示例代码和部署步骤,展示了如何使用 Diamond 协议来构建可升级的智能合约。
在之前的博客文章(part1, part2)中,我们了解到代理最初是作为一种升级合约的方式。之后,我们有了 UUPS 以提高效率,以及用于廉价克隆的最小代理。但是,如果一个可升级的合约不够用怎么办?如果需要数百个模块,每个模块都可以独立升级,而不会破坏其余模块怎么办?
这就是 Diamonds (EIP-2535 )的用武之地。
摘自 EIP 规范:https://learnblockchain.cn/docs/eips/EIPS/eip-2535
摘自 EIP 规范:https://learnblockchain.cn/docs/eips/EIPS/eip-2535
在这里我们可以看到:
FacetA
被 Diamond1
和 Diamond2
使用FacetB
被 Diamond1
和 Diamond2
使用IDiamond
接口:
每个 Diamond 必须 实现 IDiamond
接口。
diamondCut
执行的,还是通过任何其他机制执行的。interface IDiamond {
enum FacetCutAction {Add, Replace, Remove}
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}
IDiamondCut
接口:
interface IDiamondCut is IDiamond {
/// @notice 添加/替换/删除任意数量的函数,并可选择使用 delegatecall 执行一个函数
/// @param _diamondCut 包含 facet 地址和函数选择器
/// @param _init 要执行 _calldata 的合约或 facet 的地址
/// @param _calldata 一个函数调用,包括函数选择器和参数
/// _calldata 在 _init 上使用 delegatecall 执行
function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
}
IDiamondCut
。diamondCut(FacetCut[] cuts, address init, bytes calldata_)
在一个 tx 中原子性地应用 添加/替换/删除(防止不一致状态),并且是工具/UI 期望的标准入口点。其中:
1. FacetCut
: {facetAddress, action (Add|Replace|Remove), selectors[]}
.
2. Add
: 每个 selector 必须是 unset: 映射到 facetAddress
,否则 revert。
3. Replace
: 每个 selector 必须 已经存在 并且 不能 已经指向 facetAddress
,unset 或相同目标将 revert。
4. Remove
: 每个 selector 必须 存在。删除一个 unset 的 selector 将 revert
5. Immutable functions
不能被替换/删除,任何尝试都会 revert。
使用此流程来保持升级 明确、有意且可互操作。
IDiamondLoupe
接口Diamonds 必须支持通过实现 IDiamondLoupe
接口来检查 facet 和函数。
// A loupe is a small magnifying glass used to look at diamonds.
// These functions look at diamonds
// loupe 是一种用于观察钻石的小型放大镜。
// 这些函数用于观察钻石
interface IDiamondLoupe {
struct Facet {
address facetAddress;
bytes4[] functionSelectors;
}
/// @notice 获取所有 facet 地址及其四字节函数选择器。
/// @return facets_ Facet
// 获取所有 facet 地址及其四字节函数选择器。
function facets() external view returns (Facet[] memory facets_);
/// @notice 获取特定 facet 支持的所有函数选择器。
/// @param _facet The facet address.
/// @return facetFunctionSelectors_
// 获取特定 facet 支持的所有函数选择器。
function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_);
/// @notice 获取 diamond 使用的所有 facet 地址。
/// @return facetAddresses_
// 获取 diamond 使用的所有 facet 地址。
function facetAddresses() external view returns (address[] memory facetAddresses_);
/// @notice 获取支持给定 selector 的 facet。
/// @dev 如果未找到 facet,则返回 address(0)。
/// @param _functionSelector The function selector.
/// @return facetAddress_ The facet address.
// 获取支持给定 selector 的 facet。
// 如果未找到 facet,则返回 address(0)。
function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}
loupe 函数可以在用户界面软件中使用。用户界面调用这些函数来提供关于和可视化 diamonds 的信息。loupe 函数可以在部署功能、升级功能、测试和其他软件中使用。
src/LibDiamond.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IDiamondCut} from "./interfaces/IDiamondCut.sol";
library LibDiamond {
bytes32 internal constant DIAMOND_STORAGE_POSITION =
keccak256("diamond.standard.diamond.storage");
error NotContractOwner();
error FunctionNotFound();
error InitFailed(bytes returndata);
struct FacetAddressAndPosition {
address facetAddress;
uint96 functionSelectorPosition;
}
struct FacetFunctionSelectors {
bytes4[] functionSelectors;
uint256 facetAddressPosition;
}
struct DiamondStorage {
mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPos;
mapping(address => FacetFunctionSelectors) facetFunctionSelectors;
address[] facetAddresses;
address contractOwner;
}
function diamondStorage() internal pure returns (DiamondStorage storage ds) {
bytes32 pos = DIAMOND_STORAGE_POSITION;
assembly { ds.slot := pos }
}
// ------------- owner -------------
// ------------- 所有者 -------------
event OwnershipTransferred(address indexed prevOwner, address indexed newOwner);
function setContractOwner(address _newOwner) internal {
DiamondStorage storage ds = diamondStorage();
address prev = ds.contractOwner;
ds.contractOwner = _newOwner;
emit OwnershipTransferred(prev, _newOwner);
}
function enforceIsContractOwner() internal view {
if (msg.sender != diamondStorage().contractOwner) revert NotContractOwner();
}
// ------------- cut -------------
// 使用接口类型:
event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata);
function diamondCut(
IDiamondCut.FacetCut[] memory _cut,
address _init,
bytes memory _calldata
) internal {
for (uint i; i < _cut.length; i++) {
IDiamondCut.FacetCutAction action = _cut[i].action;
if (action == IDiamondCut.FacetCutAction.Add) {
addFunctions(_cut[i].facetAddress, _cut[i].functionSelectors);
} else if (action == IDiamondCut.FacetCutAction.Replace) {
replaceFunctions(_cut[i].facetAddress, _cut[i].functionSelectors);
} else if (action == IDiamondCut.FacetCutAction.Remove) {
removeFunctions(_cut[i].facetAddress, _cut[i].functionSelectors);
}
}
emit DiamondCut(_cut, _init, _calldata);
initializeDiamondCut(_init, _calldata);
}
function addFunctions(address _facet, bytes4[] memory _selectors) internal {
require(_facet != address(0), "Add facet can't be 0");
DiamondStorage storage ds = diamondStorage();
FacetFunctionSelectors storage ffs = ds.facetFunctionSelectors[_facet];
if (ffs.functionSelectors.length == 0) {
ds.facetAddresses.push(_facet);
ffs.facetAddressPosition = ds.facetAddresses.length - 1;
}
for (uint i; i < _selectors.length; i++) {
bytes4 sel = _selectors[i];
require(ds.selectorToFacetAndPos[sel].facetAddress == address(0), "Selector exists");
ds.selectorToFacetAndPos[sel] = FacetAddressAndPosition({
facetAddress: _facet,
functionSelectorPosition: uint96(ffs.functionSelectors.length)
});
ffs.functionSelectors.push(sel);
}
}
function replaceFunctions(address _facet, bytes4[] memory _selectors) internal {
require(_facet != address(0), "Replace facet can't be 0");
DiamondStorage storage ds = diamondStorage();
FacetFunctionSelectors storage ffs = ds.facetFunctionSelectors[_facet];
if (ffs.functionSelectors.length == 0) {
ds.facetAddresses.push(_facet);
ffs.facetAddressPosition = ds.facetAddresses.length - 1;
}
for (uint i; i < _selectors.length; i++) {
bytes4 sel = _selectors[i];
address oldFacet = ds.selectorToFacetAndPos[sel].facetAddress;
require(oldFacet != _facet, "Replace same facet");
_removeFunction(oldFacet, sel);
ds.selectorToFacetAndPos[sel] = FacetAddressAndPosition({
facetAddress: _facet,
functionSelectorPosition: uint96(ffs.functionSelectors.length)
});
ffs.functionSelectors.push(sel);
}
}
function removeFunctions(address _facet, bytes4[] memory _selectors) internal {
require(_facet == address(0), "Remove facet must be 0");
for (uint i; i < _selectors.length; i++) {
_removeFunction(diamondStorage().selectorToFacetAndPos[_selectors[i]].facetAddress, _selectors[i]);
}
}
function _removeFunction(address _facet, bytes4 _sel) private {
DiamondStorage storage ds = diamondStorage();
FacetAddressAndPosition memory fap = ds.selectorToFacetAndPos[_sel];
address facet = fap.facetAddress;
require(facet != address(0), "Selector !exist");
FacetFunctionSelectors storage ffs = ds.facetFunctionSelectors[facet];
uint last = ffs.functionSelectors.length - 1;
uint pos = fap.functionSelectorPosition;
if (pos != last) {
bytes4 lastSel = ffs.functionSelectors[last];
ffs.functionSelectors[pos] = lastSel;
ds.selectorToFacetAndPos[lastSel].functionSelectorPosition = uint96(pos);
}
ffs.functionSelectors.pop();
delete ds.selectorToFacetAndPos[_sel];
if (ffs.functionSelectors.length == 0) {
uint lastFacetPos = ds.facetAddresses.length - 1;
uint facetPos = ffs.facetAddressPosition;
if (facetPos != lastFacetPos) {
address lastFacetAddr = ds.facetAddresses[lastFacetPos];
ds.facetAddresses[facetPos] = lastFacetAddr;
ds.facetFunctionSelectors[lastFacetAddr].facetAddressPosition = facetPos;
}
ds.facetAddresses.pop();
delete ds.facetFunctionSelectors[facet];
}
}
function initializeDiamondCut(address _init, bytes memory _calldata) private {
if (_init == address(0)) {
require(_calldata.length == 0, "Init addr is 0");
return;
}
(bool ok, bytes memory ret) = _init.delegatecall(_calldata);
if (!ok) revert InitFailed(ret);
}
}
src/interfaces/IDiamondCut.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IDiamondCut {
enum FacetCutAction { Add, Replace, Remove }
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
/// @notice 添加/替换/删除函数 & 可选择执行 init
// 添加/替换/删除函数 & 可选择执行 init
function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}
src/interfaces/IDiamondLoupe.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IDiamondLoupe {
struct Facet { address facetAddress; bytes4[] functionSelectors; }
function facets() external view returns (Facet[] memory facets_);
function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory _selectors);
function facetAddresses() external view returns (address[] memory facetAddresses_);
function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}
src/Diamond.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {LibDiamond} from "./LibDiamond.sol";
import {IDiamondCut} from "./interfaces/IDiamondCut.sol";
/// @notice The Diamond itself: holds storage + fallback router
// @notice Diamond 本身:持有存储 + fallback 路由器
contract Diamond {
constructor(address _contractOwner, IDiamondCut.FacetCut[] memory _cut, address _init, bytes memory _calldata) {
LibDiamond.setContractOwner(_contractOwner);
LibDiamond.diamondCut(_cut, _init, _calldata);
}
// loupe & ownership are immutable if you put them here,
// but to keep it idiomatic we route everything via facets.
// 如果你把 loupe & ownership 放在这里,它们是 immutable 的,
// 但为了保持惯用性,我们通过 facets 路由一切。
fallback() external payable {
address facet = LibDiamond.diamondStorage().selectorToFacetAndPos[msg.sig].facetAddress;
if (facet == address(0)) revert LibDiamond.FunctionNotFound();
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
receive() external payable {}
}
src/facets/DiamondCutFacet.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {LibDiamond} from "../LibDiamond.sol";
import {IDiamondCut} from "../interfaces/IDiamondCut.sol";
contract DiamondCutFacet is IDiamondCut {
function diamondCut(
FacetCut[] calldata _cut,
address _init,
bytes calldata _calldata
) external override {
LibDiamond.enforceIsContractOwner();
LibDiamond.diamondCut(_cut, _init, _calldata);
}
}
src/facets/DiamondLoupeFacet.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {LibDiamond} from "../LibDiamond.sol";
import {IDiamondLoupe} from "../interfaces/IDiamondLoupe.sol";
contract DiamondLoupeFacet is IDiamondLoupe {
function facets() external view override returns (Facet[] memory facets_) {
LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
address[] memory addrs = ds.facetAddresses;
facets_ = new Facet[](addrs.length);
for (uint i; i < addrs.length; i++) {
bytes4[] memory selectors = ds.facetFunctionSelectors[addrs[i]].functionSelectors;
facets_[i] = Facet({facetAddress: addrs[i], functionSelectors: selectors});
}
}
function facetFunctionSelectors(address _facet) external view override returns (bytes4[] memory _selectors) {
_selectors = LibDiamond.diamondStorage().facetFunctionSelectors[_facet].functionSelectors;
}
function facetAddresses() external view override returns (address[] memory facetAddresses_) {
facetAddresses_ = LibDiamond.diamondStorage().facetAddresses;
}
function facetAddress(bytes4 _sel) external view override returns (address facetAddress_) {
facetAddress_ = LibDiamond.diamondStorage().selectorToFacetAndPos[_sel].facetAddress;
}
}
src/facets/OwnershipFacet.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {LibDiamond} from "../LibDiamond.sol";
contract OwnershipFacet {
function owner() external view returns (address) {
return LibDiamond.diamondStorage().contractOwner;
}
function transferOwnership(address _newOwner) external {
LibDiamond.enforceIsContractOwner();
LibDiamond.setContractOwner(_newOwner);
}
}
src/facets/ExampleFacet.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract ExampleFacet {
uint256 internal counter;
function increment() external { unchecked { counter++; } }
function getCounter() external view returns (uint256) { return counter; }
}
让我们编写一个脚本来设置所有的 facets 和 diamond:
script/deployDiamond.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import {Diamond} from "../src/Diamond.sol";
import {IDiamondCut} from "../src/interfaces/IDiamondCut.sol";
import {DiamondCutFacet} from "../src/facets/DiamondCutFacet.sol";
import {DiamondLoupeFacet} from "../src/facets/DiamondLoupeFacet.sol";
import {OwnershipFacet} from "../src/facets/OwnershipFacet.sol";
import {ExampleFacet} from "../src/facets/ExampleFacet.sol";
contract DeployDiamond is Script {
function run() external {
// load deployer key from env (use ANVIL default or your own)
// 从 env 加载部署者密钥(使用 ANVIL 默认值或你自己的)
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
vm.startBroadcast(pk);
// 1) deploy facets
// 1) 部署 facets
DiamondCutFacet cutFacet = new DiamondCutFacet();
DiamondLoupeFacet loupeFacet = new DiamondLoupeFacet();
OwnershipFacet ownFacet = new OwnershipFacet();
ExampleFacet exampleFacet = new ExampleFacet();
// 2) build the facet cuts
// 2) 构建 facet cuts
IDiamondCut.FacetCut [] memory cut = new IDiamondCut.FacetCut[](4);
// DiamondCutFacet
{
bytes4 [] memory selectors = new bytes4[](1);
selectors[0] = DiamondCutFacet.diamondCut.selector;
cut[0] = IDiamondCut.FacetCut({
facetAddress: address(cutFacet),
action: IDiamondCut.FacetCutAction.Add,
functionSelectors: selectors
});
}
// DiamondLoupeFacet
{
bytes4[] memory selectors = new bytes4[](4);
selectors[0] = DiamondLoupeFacet.facets.selector;
selectors[1] = DiamondLoupeFacet.facetFunctionSelectors.selector;
selectors[2] = DiamondLoupeFacet.facetAddresses.selector;
selectors[3] = DiamondLoupeFacet.facetAddress.selector;
cut[1] = IDiamondCut.FacetCut({
facetAddress: address(loupeFacet),
action: IDiamondCut.FacetCutAction.Add,
functionSelectors: selectors
});
}
// OwnershipFacet
{
bytes4 [] memory selectors = new bytes4[](2);
selectors[0] = OwnershipFacet.owner.selector;
selectors[1] = OwnershipFacet.transferOwnership.selector;
cut[2] = IDiamondCut.FacetCut({
facetAddress: address(ownFacet),
action: IDiamondCut.FacetCutAction.Add,
functionSelectors: selectors
});
}
// ExampleFacet
{
bytes4 [] memory selectors = new bytes4[](2);
selectors[0] = ExampleFacet.increment.selector;
selectors[1] = ExampleFacet.getCounter.selector;
cut[3] = IDiamondCut.FacetCut({
facetAddress: address(exampleFacet),
action: IDiamondCut.FacetCutAction.Add,
functionSelectors: selectors
});
}
// 3) deploy diamond with the cut
// 3) 使用 cut 部署 diamond
Diamond diamond = new Diamond(
deployer,
cut,
address(0), // no init facet
"" // no init calldata
);
vm.stopBroadcast();
console.log("Diamond deployed at:", address(diamond));
}
}
运行 anvil
anvil
在 forge
项目的其他终端中运行:
forge script script/deployDiamond.s.sol:DeployDiamond \
--rpc-url http://localhost:8545 \
--broadcast
这应该会部署 facets 和 diamond。
你应该会看到类似这样的东西:
让我们执行一些交易:
## Read the counter (should be 0 initially)
// 读取计数器(最初应为 0)
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 \
"getCounter()(uint256)" \
--rpc-url http://localhost:8545
## Increment the counter (must be a tx)
// 增加计数器(必须是一个交易)
cast send 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 \
"increment()" \
--rpc-url http://localhost:8545 \
--private-key <YOUR_ANVIL_PK>
## Check again. (The output should be 1)
// 再次检查。(输出应为 1)
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 \
"getCounter()(uint256)" \
--rpc-url http://localhost:8545
## Lets call other facet
// 让我们调用其他 facet
## Who is the owner? (should be the address you provided in the script)
// 谁是所有者?(应该是你在脚本中提供的地址)
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 \
"owner()(address)" \
--rpc-url http://localhost:8545
让我们添加一个新的 facet:
src/facets/newFacet.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract ExampleFacetV2 {
uint256 public counter;
function increment() external {
counter++;
}
function getCounter() external view returns (uint256) {
return counter;
}
// New functionality!
// 新功能!
function decrement() external {
require(counter > 0, "already zero");
counter--;
}
}
让我们部署新的 facet:
forge create src/facets/newFacet.sol:ExampleFacetV2 \
--rpc-url http://localhost:8545 \
--private-key <YOUR_ANVIL_PK> --broadcast
## The output should be something like:
// 输出应如下所示:
## [⠊] Compiling...
## No files changed, compilation skipped
// 没有文件更改,跳过编译
## Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
## Deployed to: 0x0165878A594ca255338adfa4d48449f69242Eb8F
## Transaction hash: 0xd78b181b3e57cab0f4d693b7e9963fff3f3a5a68d9d532274a0a6196cbf7bf13
通过 diamondCut
升级
## Get the 4 bytes of the new selector
// 获取新 selector 的 4 个字节
cast sig "decrement()"
## → 0x2baeceb7
## upgrage
// 升级
cast send 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 \
"diamondCut((address,uint8,bytes4[])[],address,bytes)" \
'[(0x0165878A594ca255338adfa4d48449f69242Eb8F,0,[0x2baeceb7])]' \
0x0000000000000000000000000000000000000000 \
0x \
--rpc-url http://localhost:8545 \
--private-key <YOUR_ANVIL_PK>
## This Tx should Succeed
// 此 Tx 应该成功
让我们尝试执行 facets 之间的混合方法:
## Increment first
// 首先增加
cast send 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "increment()" \
--rpc-url http://localhost:8545 --private-key <YOUR_ANVIL_PK>
## Check counter (should be 2 now, adds 1 the storage)
// 检查计数器(现在应为 2,在存储中添加 1)
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "getCounter()(uint256)" \
--rpc-url http://localhost:8545
## → should be 1
// 应该是 1
## Call the new function
// 调用新函数
cast send 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "decrement()" \
--rpc-url http://localhost:8545 --private-key <YOUR_ANVIL_PK>
## Check counter again
// 再次检查计数器
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "getCounter()(uint256)" \
--rpc-url http://localhost:8545
## → should be back to 1
// 应该回到 1
升级前:
0xd09de08a -> ExampleFacet // increment()
0x06661abd -> ExampleFacet // getCounter()
然后我们部署了一个新的 facet ExampleFacetV2
,它添加了一个新函数:
decrement()
→ selector, e.g. 0x2baeceb7
并调用了
diamondCut(
[(ExampleFacetV2, Add(0), [0x2baeceb7])],
address(0),
0x
)
升级后,diamond 的 selector 映射现在看起来像:
0xd09de08a -> ExampleFacet // increment()
0x06661abd -> ExampleFacet // getCounter()
0x2baeceb7 -> ExampleFacetV2 // decrement()
快速完整性检查:
## Should output the first address of the example facet
// 应该输出 example facet 的第一个地址
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "facetAddress(bytes4)(address)" 0xd09de08a --rpc-url http://localhost:8545
## Should output the new address of the exampleV2 facet
// 应该输出 exampleV2 facet 的新地址
cast call 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 "facetAddress(bytes4)(address)" 0xd09de08a --rpc-url http://localhost:8545
注意: 我们在这里没有触及存储布局,实际上两个 facet 示例都应该使用相同的存储布局。我们不会在这里触及它,以免使这篇文章过于庞大。快速示例:
library CounterStorage {
// random slot: keccak256("example.counter.storage")
// 随机槽:keccak256("example.counter.storage")
bytes32 internal constant SLOT = keccak256("example.counter.storage");
struct Layout {
uint256 counter;
}
function layout() internal pure returns (Layout storage l) {
bytes32 slot = SLOT;
assembly { l.slot := slot }
}
}
这可以是它们都使用的布局。Facets 必须在存储布局上达成一致。如果你不匹配布局,你的应用将损坏存储。始终使用库隔离存储。
开发者使用 Diamonds 而不是 “just another proxy” 有很多原因:
一个 diamond 从一个地址暴露其所有功能。这使得部署、测试以及与其他智能合约、钱包和 UI 的集成更加简单。
EVM 将合约字节码大小限制为 24KB。使用 Diamonds,你可以通过将逻辑拆分为 facets 同时保持一切在一个地址可访问来扩展超出此限制。
大型系统可以分解为独立的 facets,每个 facet 处理一个特定的问题,同时仍然共享相同的存储。这使得事情有条理、更容易理解和 gas 高效。
可升级的 diamonds 可以添加、替换或删除功能,而无需触及现有部分。你可以随着时间的推移添加多少功能没有上限。
并非每个系统都需要永远的可升级性。你可以将 diamond 部署为 immutable,或者稍后使可升级的 diamond immutable 以获得额外的信任保证。
Diamonds 不需要一切都重新部署。你可以从已部署的合约中编写一个 diamond,有效地从链上代码构建自定义平台或库。
Diamonds 是什么: 一个合约(diamond),它使用 delegatecall
将函数选择器路由到许多无状态 facets,因此所有状态都存在于一个地址。
它们如何工作: diamond 的 fallback 读取 msg.sig
→ 在 selector 映射中查找 facet → delegatecall
进入它 → 返回数据,就像 diamond 实现了该函数一样。
核心接口:
IDiamondCut
→ 标准的、原子性的添加/替换/删除 selectors(+ 可选的 init)。IDiamondLoupe
→ 查询 facets/selectors 以进行 UI、工具和完整性检查。为什么要使用它们: 打破 24KB 限制,为所有内容保留一个地址,将大型代码库组织成模块,独立升级部件,可选地 immutable 地冻结,甚至可以从现有的链上合约进行组合。
注意事项: 存储布局必须共享且明确;升级需要治理;审计涵盖更多表面;注意 selector 冲突和 init 排序。
心智模型: 一个地址,多个模块,共享状态。
- 原文链接: medium.com/@andrey_obruc...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!