Что такое EIP2535 Diamonds?

Что такое EIP2535 Diamonds?

EIP2535 Diamonds (бриллианты), также известный как Diamond Standard (бриллантовый стандарт), предложенный Ником Маджем (Nick Mudge) , представляет собой новый обновляемый прокси-шаблон, который направлен на решение двух насущных проблем смарт-контрактов написанных на solidity;

  • Модульность: Это способность системы функционировать в виде отдельных частей. Большинство традиционных смарт-контрактов не могут быть модульными, потому что они всегда зависят друг от друга, особенно в крупных системах. Если система не является модульной, то значительно затрудняется выявление и устранение возникающих неисправностей. Прокси-сервер Diamond позволяет использовать специальные “грани” (это обычные смарт-контракты, подключенные к прокси-серверу Diamond, но содержащие только логику и не имеющие собственного хранилища).

%D0%B3%D1%80%D0%B0%D0%BD%D0%B8_1

  • Ограничение по размеру контракта: Большинство EVM сетей имеют ограничение на объем байт-кода контракта (максимум 24 Кб), это усложняет создание надежных систем, поскольку весь их код не может поместиться в один контракт. Diamond Standard решает эту проблему с помощью своей структуры “граней”, каждая грань сама по себе представляет собой отдельный контракт, который имеет собственную логику, поэтому существует бесконечное количество граней, которые могут быть развернуты, и все они будут подключены к одному прокси-серверу Diamond.

%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%203

Таким образом, все логические контракты (грани) соответствуют ограничению на размер байт кода ≤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

3 Likes

Это действительно интересный подход, который позволяет создавать более сложные и масштабируемые системы, преодолевая ограничения по размеру байт-кода контракта. Учитывая что можно еще купить прокси, то это вообще получается безпроиграшный вариант. Разделяя логику между различными гранями, можно улучшить читаемость, обслуживание и безопасность кода.