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 true
aufnehmen 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?
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:
"0x5B2063246F2191f18F2675ceDB8b28102e957458"
neben der Schaltfläche [whitelistAddress] einLaufergebnisse 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.
Es gibt einige modernere Alternativen, die jetzt, da Gas so viel höher ist, unbedingt in Betracht gezogen werden müssen.
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
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");
....
blockchaindotsol
Transaction cost: 20784 gas
, im Gegensatz zu demTransaction 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?Schnitzer
Schnitzer
blockchaindotsol
blockchaindotsol
Schwebender Fels
Bobbi Bennett