Как внести в белый список до 50 000 адресов в одном контракте?

Как лучше всего заносить в белый список большое количество адресов в контракте. Например, если бы я хотел включить 50 тыс. адресов в сопоставление, которое будет отражать trueтолько 50 тыс. адресов, будет ли это оптимальным способом? Сколько это будет стоить за адрес?

mapping (address => bool) userAddr;

function whitelistAddress (address user) onlyOwner { userAddr[user] = true; }

Включение 50 000 адресов, жестко закодированных в контракте, несомненно, приведет к превышению лимита блочного газа. Как мне точно рассчитать, сколько это будет стоить

Ответы (3)

Быстрая нижняя граница

Вы можете установить нижнюю границу стоимости, потому что каждый адрес будет включать как минимум один SSTORE. Таким образом, затраты на жесткое кодирование 50 000 адресов по 20 кг каждый составят в общей сложности более 1 гигабайта. Последний лимит блока составляет 6,7 мегагаза. Он не подойдет, далеко не так.

Gsset — 20000 газа — оплачивается за операцию SSTORE, когда значение хранилища установлено ненулевым с нуля.

источник: Yellow Paper, Приложение G.


Практическая стоимость

Итак, давайте посмотрим на практическую стоимость постепенного добавления адресов в белый список. Remix — отличный способ быстро оценить затраты на газ для вашей функции.

Используя этот контракт:

pragma solidity ^0.4.15;

contract Owned {
    address owner;

    function Owned() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }
}
contract Whitelist is Owned {
    mapping (address => bool) userAddr;

    function whitelistAddress (address user) onlyOwner {
        userAddr[user] = true;
    }
}

Я запустил функцию следующим образом:

  1. Выберите среду «Javascript VM».
  2. Нажмите [Создать] в разделе «Белый список» на правой панели.
  3. Введите "0x5B2063246F2191f18F2675ceDB8b28102e957458"рядом с кнопкой [whitelistAddress]
  4. Нажмите кнопку [адрес белого списка]

Запуск whitelistAddress(...)приводит к:Transaction cost: 43464 gas

Общая стоимость:50k transactions * 43k gas ~= 2 Gigagas

При текущей « безопасной низкой » стоимости 0,5 gwei, что соответствует примерно 1 эфиру от общей стоимости газа.


Пакетирование для незначительного улучшения

Некоторые из этих вызовов для добавления адреса в белый список являются накладными: исходный вызов функции, проверка владельца и т. д. Давайте выясним, сколько.

pragma solidity ^0.4.15;

contract Whitelist is Owned {
    mapping (address => bool) userAddr;

    function whitelistAddress (address[] users) onlyOwner {
        for (uint i = 0; i < users.length; i++) {
            userAddr[users[i]] = true;
        }
    }
}

Вызов этого со списком из четырех адресов стоит 109833 газа, в результате чего стоимость каждого адреса составляет всего 27458,25 газа. Мы не добьемся большего успеха в явном белом списке, поскольку нижняя граница составляет 20 000 газа на адрес.

Используя этот метод, общая стоимость эфира по цене Safe Low снизилась примерно до:

50k addresses * 27k gas * 0.5 gigawei ~= 0.7 Ether


Более странные альтернативы

Может быть, у вас на самом деле нет белого списка, может быть, это черный список или список гостей, где случайные ложные срабатывания не так уж плохи. Тогда фильтр Блума может быть разумным решением. Настройка фильтра Блума требует слишком много знаний о вашем конкретном случае использования, но это может легко снизить общую стоимость в 1000 или 10 000 раз.

Действительно замечательный ответ. Все это имеет смысл, так что спасибо, хотя у меня есть один вопрос. При копировании этого первого блока кода в Remix я вижу Transaction cost: 20784 gas, а не Transaction cost: 43464 gas, как вы сказали. Я просматриваю Оценки газа -> Внешний -> адрес белого списка (адрес). Есть ли другое место, где оценивается газ?
Я не уверен, как ремикс генерирует такую ​​оценку. Я просто запустил транзакцию, нажав кнопку [whitelistAddress] после ввода «0x5B2063246F2191f18F2675ceDB8b28102e957458» рядом с ней. Я отредактирую ответ, чтобы добавить эти шаги.
Вам нужно ввести адрес с двойными кавычками. Поле ввода ожидает содержимое в формате JSON.
Когда вы получаете от этого газ, можете ли вы просто игнорировать «стоимость исполнения»? Когда это вступает в игру? Сейчас я вижу 43464, но есть и стоимость исполнения.
Поцарапайте это, это ответило на это. Спасибо.
Будет ли использоваться аналогичный подход для адресов из белого списка с индивидуальными лимитами взносов?
Классический фильтр Блума не будет работать. Если есть вероятность ложного срабатывания, злоумышленник может просто продолжать генерировать адреса, пока не наткнется на один из них.

Есть несколько более современных альтернатив, которые важно учитывать сейчас, когда газ намного дороже.

Белый список на основе корня Merkle (низкая стоимость, умеренная гибкость)

Используйте все адреса в вашем списке для создания дерева Меркла. Затем попросите пользователя отправить свое доказательство (может использоваться на внешнем интерфейсе, не является конфиденциальным, поскольку только владелец адреса может использовать свое доказательство) вместе с вызовом монетного двора.

Я считаю это средней гибкостью, потому что, если вы хотите вообще изменить список, вам нужно обновить корень в цепочке.

Код генерации (js)

      const newMerkle = new MerkleTree(
        ['0x1...', '0x2...'].map((token: string) => hashToken(token)),
        keccak256,
        {
          duplicateOdd: false,
          hashLeaves: false,
          isBitcoinTree: false,
          sortLeaves: false,
          sortPairs: true,
          sort: false,
        }
      )

export function hashToken(account: string) {
  return Buffer.from(ethers.utils.solidityKeccak256(["address"], [account]).slice(2), "hex");
}

Код подтверждения (надежность)

    function mintPresale(uint256 _quantity, bytes32[] calldata _proof)
        external
        payable
    {
        require(_verify(_leaf(msg.sender), _proof), "Invalid merkle proof");
        _mint(_quantity, msg.sender);
    }
    function _verify(bytes32 leaf_, bytes32[] memory _proof)
        internal
        view
        returns (bool)
    {
        return MerkleProof.verify(_proof, root, leaf_);
    }

Этот фрагмент использует код из библиотеки OpenZeppelin Merkle Proof и merkletreejs.

Белый список на основе подписи (минимальная стоимость, высокая гибкость)

Создавайте подписи заранее или живите на серверной части и предоставляйте их пользователю для включения в свой запрос монетного двора. Они также не являются конфиденциальными, поэтому их можно обслуживать на внешнем интерфейсе без какой-либо аутентификации.

Код подписи (машинопись)

export default async function signWhitelist(
    chainId: number,
    contractAddress: string,
    whitelistKey: SignerWithAddress,
    mintingAddress: string,
    nonce: number
) {
    const domain = {
        name: '[YOUR_CONTRACT_NAME}',
        version: '1',
        chainId,
        verifyingContract: contractAddress,
    }

    const types = {
        Minter: [
            { name: 'wallet', type: 'address' },
            { name: 'nonce', type: 'uint256' },
        ],
    }

    const sig = await whitelistKey._signTypedData(domain, types, {
        wallet: mintingAddress,
        nonce,
    })

    return sig
}

Подтверждающий код (Solidity)

    modifier requiresWhitelist(
        bytes calldata signature,
        uint256 nonce
    ) {
        // Verify EIP-712 signature by recreating the data structure
        // that we signed on the client side, and then using that to recover
        // the address that signed the signature for this data.
        bytes32 structHash = keccak256(
            abi.encode(MINTER_TYPEHASH, msg.sender, nonce)
        );
        bytes32 digest = toTypedMessageHash(structHash); /*Calculate EIP712 digest*/
        require(!signatureUsed[digest], "signature used");
        signatureUsed[digest] = true;
        // Use the recover method to see what address was used to create
        // the signature on this data.
        // Note that if the digest doesn't exactly match what was signed we'll
        // get a random recovered address.
        address recoveredAddress = digest.recover(signature);
        require(
            hasRole(WHITELISTING_ROLE, recoveredAddress),
            "Invalid Signature"
        );
        _;
    }

Эти фрагменты адаптированы из этого репозитория: https://github.com/msfeldstein/EIP712-whitelisting/blob/main/contracts/EIP712Whitelisting.sol .

это довольно легко. вы делаете это так. через подписи. нужен бэкенд....

Функция matchAddresSigner (хэш байтов 32, подпись байтов памяти) возвращает частный вид (bool) { return _signerAddress == hash.recover (подпись); }

с

require(matchAddresSigner(hash, sig), "нет прямого монетного двора");

....

не могли бы вы немного объяснить, как это сделать. как это можно использовать для белого адреса?