Wie kann ich bis zu 50.000 Adressen in einem einzigen Vertrag auf die Whitelist setzen?

Was ist die beste Vorgehensweise beim Whitelisting einer großen Anzahl von Adressen in einem Vertrag? Wenn ich beispielsweise 50.000 Adressen in eine Zuordnung trueaufnehmen möchte, die nur die 50.000 Adressen widerspiegelt, wäre dies der optimale Weg? Wie viel würde das pro Adresse kosten?

mapping (address => bool) userAddr;

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

Die Aufnahme von 50.000 Adressen, die in den Vertrag festcodiert sind, würde zweifellos die Blockgasgrenze erreichen. Wie berechne ich genau, wie viel das kosten würde?

Antworten (3)

Schnelle untere Grenze

Sie können die Kosten nach unten begrenzen, da jede Adresse mindestens einen SSTORE enthält. Die Kosten für die Festcodierung von 50.000 Adressen mit jeweils 20 kg wären also insgesamt mehr als 1 Gigagas. Das neueste Blocklimit liegt bei 6,7 Megagas. Es wird schon lange nicht mehr passen.

Gsset – 20000 Gas – Bezahlt für einen SSTORE-Vorgang, wenn der Speicherwert von null auf einen Wert ungleich null gesetzt wird

Quelle: Yellow Paper, Anhang G


Praktische Kosten

Sehen wir uns also die praktischen Kosten für das inkrementelle Hinzufügen von Whitelist-Adressen an. Remix ist eine hervorragende Möglichkeit, die Gaskosten für Ihre Veranstaltung schnell abzuschätzen.

Mit diesem Vertrag:

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;
    }
}

Ich habe die Funktion so ausgeführt:

  1. Wählen Sie die Umgebung "Javascript VM".
  2. Klicken Sie in der rechten Leiste unter Whitelist auf [Create].
  3. Geben Sie "0x5B2063246F2191f18F2675ceDB8b28102e957458"neben der Schaltfläche [whitelistAddress] ein
  4. Klicken Sie auf die Schaltfläche [whitelistAddress].

Laufergebnisse whitelistAddress(...)in:Transaction cost: 43464 gas

Gesamtkosten:50k transactions * 43k gas ~= 2 Gigagas

Bei aktuellen „ Safe Low “-Kosten von 0,5 gwei entspricht das etwa 1 Äther der gesamten Gaskosten.


Batching für marginale Verbesserung

Ein Teil des Aufrufs zum Hinzufügen einer Whitelist-Adresse ist Overhead: der ursprüngliche Funktionsaufruf, die Überprüfung des Eigentümers usw. Lassen Sie uns herausfinden, wie viel.

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;
        }
    }
}

Der Anruf mit einer Liste von vier Adressen kostet 109833 Gas, was zu Kosten pro Adresse von nur 27458,25 Gas führt. Wir werden auf einer expliziten Whitelist nicht viel besser abschneiden, da die Untergrenze bei 20.000 Gas pro Adresse liegt.

Mit dieser Methode sind die gesamten Etherkosten zum Safe Low-Preis auf etwa gesunken:

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


Seltsamere Alternativen

Vielleicht haben Sie nicht wirklich eine Whitelist, vielleicht ist es eine Blacklist oder eine Gästeliste, bei der gelegentliche Fehlalarme nicht so schlimm sind. Dann könnte ein Bloomfilter eine sinnvolle Lösung sein. Das Optimieren des Bloom-Filters erfordert zu viel Wissen über Ihren spezifischen Anwendungsfall, aber es könnte die Gesamtkosten leicht um das 1.000-fache oder 10.000-fache reduzieren.

Wirklich wunderbare Antwort. Es macht alles Sinn, also danke, obwohl ich eine Frage habe. Wenn ich diesen ersten Codeblock in Remix kopiere, sehe ich ein Transaction cost: 20784 gas, im Gegensatz zu dem Transaction cost: 43464 gas, wie Sie gesagt haben. Ich schaue mir Gas Estimates -> External -> whitelistAddress (Adresse) an. Gibt es einen anderen Ort, an dem das Gas geschätzt wird?
Ich bin mir nicht sicher, wie Remix diese Schätzung generiert. Ich habe gerade die Transaktion ausgeführt, indem ich auf die Schaltfläche [whitelistAddress] geklickt habe, nachdem ich „0x5B2063246F2191f18F2675ceDB8b28102e957458“ daneben eingegeben habe. Ich werde die Antwort bearbeiten, um diese Schritte hinzuzufügen.
Sie müssen die Adresse mit den doppelten Anführungszeichen eingeben. Das Eingabefeld erwartet JSON-codierte Inhalte.
Wenn Sie davon Gas bekommen, können Sie dann einfach die „Ausführungskosten“ ignorieren? Wann kommt das ins Spiel? Ich sehe jetzt die 43464, aber es kostet auch die Ausführung.
Streich das, das hat es beantwortet. Vielen Dank.
Würde ein ähnlicher Ansatz für Whitelist-Adressen mit individuellen Beitragsobergrenzen angewendet werden?
Der klassische Bloom-Filter funktioniert nicht. Wenn die Möglichkeit eines Fehlalarms besteht, kann ein Angreifer einfach so lange Adressen generieren, bis er eine trifft.

Es gibt einige modernere Alternativen, die jetzt, da Gas so viel höher ist, unbedingt in Betracht gezogen werden müssen.

Merkle Root-basierte Whitelist (geringe Kosten, mäßige Flexibilität)

Verwenden Sie alle Adressen auf Ihrer Liste, um einen Merkle-Baum zu generieren. Lassen Sie den Benutzer dann seinen Nachweis (kann im Front-End bereitgestellt werden, nicht sensibel, da nur der Adressinhaber seinen Nachweis verwenden kann) zusammen mit dem Mint-Aufruf einreichen.

Ich betrachte diese Flexibilität als mittel, denn wenn Sie die Liste überhaupt ändern möchten, müssen Sie die Root-on-Chain aktualisieren.

Generierungscode (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");
}

Verifizierungscode (Festigkeit)

    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_);
    }

Dieses Snippet verwendet Code aus der OpenZeppelin Merkle Proof-Bibliothek und merkletreejs

Signaturbasierte Whitelist (minimale Kosten, hochflexibel)

Generieren Sie Signaturen im Voraus oder live auf einem Backend und stellen Sie sie dem Benutzer zur Verfügung, damit er sie in seinen Mint-Aufruf aufnehmen kann. Diese sind auch nicht sensibel, sodass sie ohne Authentifizierung am Frontend bereitgestellt werden können.

Signaturcode (Typoskript)

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
}

Verifizierungscode (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"
        );
        _;
    }

Diese Snippets sind aus diesem Repo angepasst: https://github.com/msfeldstein/EIP712-whitelisting/blob/main/contracts/EIP712Whitelisting.sol

es ist ziemlich einfach. du machst das so. über Unterschriften. Du brauchst ein Backend....

Funktion matchAddresSigner (Bytes32-Hash, Bytes-Speichersignatur) Private Ansicht gibt zurück (bool) { return _signerAddress == hash.recover (signature); }

mit

require(matchAdresSigner(hash, sig), "no direct mint");

....

kannst du mir bitte kurz erklären wie das geht. Wie kann dies zur Whitelist-Adresse verwendet werden?