Что такое EIP2535 Diamonds?
EIP2535 Diamonds (бриллианты), также известный как Diamond Standard (бриллантовый стандарт), предложенный Ником Маджем (Nick Mudge) , представляет собой новый обновляемый прокси-шаблон, который направлен на решение двух насущных проблем смарт-контрактов написанных на solidity;
- Модульность: Это способность системы функционировать в виде отдельных частей. Большинство традиционных смарт-контрактов не могут быть модульными, потому что они всегда зависят друг от друга, особенно в крупных системах. Если система не является модульной, то значительно затрудняется выявление и устранение возникающих неисправностей. Прокси-сервер Diamond позволяет использовать специальные “грани” (это обычные смарт-контракты, подключенные к прокси-серверу Diamond, но содержащие только логику и не имеющие собственного хранилища).
- Ограничение по размеру контракта: Большинство EVM сетей имеют ограничение на объем байт-кода контракта (максимум 24 Кб), это усложняет создание надежных систем, поскольку весь их код не может поместиться в один контракт. Diamond Standard решает эту проблему с помощью своей структуры “граней”, каждая грань сама по себе представляет собой отдельный контракт, который имеет собственную логику, поэтому существует бесконечное количество граней, которые могут быть развернуты, и все они будут подключены к одному прокси-серверу Diamond.
Таким образом, все логические контракты (грани) соответствуют ограничению на размер байт кода ≤24 КБ. Если лимит на размер кода превышен на одном из ваших контрактов, вы можете просто развернуть новую “грань” и добавить ее в ваш “бриллиант” (путем обновления).
Для взаимодействия с логическими контрактами (гранью) используется Diamond прокси-контракт, который, в свою очередь, имеет 3 функции:
- Он ищет, где хранится ваш селектор функций, и извлекает адрес нужной грани.
- После получения адреса грани, в которой реализован этот селектор функций, он выполняет вызов делегата (delegate call) по этому адресу в контексте хранилища Diamond и возвращает все выходные данные вызывающему объекту (если они есть).
- Возможность изменения вашего “бриллианта”. Для этого доступны функции Add,Remove и Replace.
DiamondLoupeFacet : Loupe — это увеличительное стекло, используемое для осмотра бриллиантов. Эта грань содержит функции, помогающие проверить текущее состояние вашего бриллианта, предоставляя данные о текущем состоянии граней и переключателей функций.
DiamondCutFacet : грань, которая позволяет вам вносить изменения в ваш бриллиант.
В настоящее время можно использовать 3 готовых шаблона “брилланта”. Больше информации о них можете найти здесь.
Далее мы опишем процесс развертывания токена CRC20, совместимого с Diamond прокси, в пространстве Conflux Core (тот же код также можно развернуть в Conflux eSpace ). Полный код находится здесь. Обратите внимание, что он использует шаблон Diamond-3.
В этом примере кода для хранения переменных используется шаблон AppStorage.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {LibDiamond} from "./LibDiamond.sol";
struct AppStorage {
mapping(address => uint256) _balances;
mapping(address => mapping(address => uint256)) _allowances;
uint256 _totalSupply;
string _name;
string _symbol;
}
library LibAppStorage {
function diamondStorage() internal pure returns (AppStorage storage ds) {
assembly {
ds.slot := 0
}
}
}
contract Modifiers {
AppStorage internal s;
modifier onlyOwner() {
LibDiamond.enforceIsContractOwner();
_;
}
}
Структура AppStorage в LibAppStorage.sol хранит данные о взаимозаменяемом токене. Это связано с тем, что логические контракты не могут иметь собственного хранилища, но могут содержать логику, которая изменяет и получает доступ к хранилищу.
Основная грань называется DiamondToken.sol и содержит основную логику полного “бриллианта” CRC20. Обратите внимание, что для того, чтобы грань могла изменить или получить доступ к шаблонам AppStorage, ей необходимо явно определить структурную переменную, подобную этой.
AppStorage internal varName
Затем вы можете использовать varNameдля получения доступа или внесения изменений, как показано ниже:
// SPDX-License-Identifier: MIT
//Code Snippet of DiamondToken.sol
AppStorage internal s;
function transferFrom(
address sender,
address recipient,
uint256 amount
) public returns (bool) {
_transfer(sender, recipient, amount);
uint256 currentAllowance = s._allowances[sender][msg.sender];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
unchecked {
_approve(sender, msg.sender, currentAllowance - amount);
}
return true;
}
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
_approve(msg.sender, spender, s._allowances[msg.sender][spender] + addedValue);
return true;
}
function name() public view returns(string memory){
return s._name;
}
Чтобы развернуть “бриллиант”, вам нужно сначала развернуть DiamodCutFacet, а затем привязать его к контракту Proxy Diamond (Diamond.sol). Далее можно обновить “бриллиант” с помощью других граней (DiamondcutFacet, DiamondToken и DiamondloupeFacet). Чтобы упростить задачу, для этого доступен специальный скрипт.
// deploy DiamondCutFacet
const DiamondCut = cfx.Contract(getArtifacts("DiamondCutFacet"));
const txReceipt = await DiamondCut.constructor()
.sendTransaction({ from: acct })
.executed();
// deploy Diamond
const Diamond = cfx.Contract(getArtifacts("Diamond"));
const txReceipt2 = await Diamond.constructor(
acct.toString(),
txReceipt.contractCreated,
"DiamondToken",
"DMT"
)
.sendTransaction({ from: acct })
.executed();
DiamondAddress = txReceipt2.contractCreated;
//Deploy facets indiviually
const FacetNames = ["OwnershipFacet", "DiamondLoupeFacet", "DiamondToken"];
const cut = [];
for (const FacetName of FacetNames) {
const Facet = cfx.Contract(getArtifacts(FacetName));
const txReceipt = await Facet.constructor()
.sendTransaction({ from: acct })
.executed();
cut.push({
facetAddress: txReceipt.contractCreated,
action: FacetCutAction.Add,
functionSelectors: await getSelectors(FacetName),
});
}
// upgrade diamond with facets
const DiamondCutFacet = cfx.Contract({
abi: getAbi("DiamondCutFacet"),
address: DiamondAddress,
});
let tx;
//@ts-ignore
tx = await DiamondCutFacet.diamondCut(
cut,
ethers.constants.AddressZero,
"0x"
).sendTransaction({ from: acct });
Образец был развернут и проверен здесь.
На данный момент много больших и надежных протоколов разработаны на базе Siamond Standars, список проектов доступен здесь.
Оригинал статьи: https://medium.com/conflux-network/deploying-eip2535-diamond-proxy-contracts-on-conflux-6647dcf436f6