¿Cuál es la mejor práctica para incluir en la lista blanca una gran cantidad de direcciones en un contrato? Por ejemplo, si quisiera incluir 50k direcciones en un mapeo que reflejaría true
solo las 50k direcciones, ¿sería esta la forma óptima? ¿Cuánto costaría esto, por dirección?
mapping (address => bool) userAddr;
function whitelistAddress (address user) onlyOwner { userAddr[user] = true; }
Incluir 50k direcciones codificadas en el contrato alcanzaría el límite de gas del bloque, sin duda. ¿Cómo calculo exactamente cuánto costaría esto?
Límite inferior rápido
Puede poner un límite inferior al costo, porque cada dirección incluirá una SSTORE, como mínimo. Entonces, el costo de codificar 50k direcciones, a 20 kg cada una, sería más de 1 Gigagas en total. El último límite de bloque es de 6,7 Megagas. No encajará, por asomo.
Gsset - 20000 gas - Pagado por una operación SSTORE cuando el valor de almacenamiento se establece en distinto de cero desde cero
fuente: Libro Amarillo, Apéndice G
Costo práctico
Entonces, echemos un vistazo al costo práctico de agregar gradualmente direcciones de lista blanca. Remix es una excelente manera de estimar rápidamente los costos de gas para su función.
Usando este contrato:
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;
}
}
Ejecuté la función de esta manera:
"0x5B2063246F2191f18F2675ceDB8b28102e957458"
junto al botón [dirección de la lista blanca]Ejecutar whitelistAddress(...)
resultados en:Transaction cost: 43464 gas
Coste total:50k transactions * 43k gas ~= 2 Gigagas
A los costos actuales de " bajo nivel de seguridad " de 0,5 gwei, eso equivale a aproximadamente 1 éter de los costos totales de gas.
Dosificación para mejora marginal
Parte de esa llamada para agregar una dirección en la lista blanca es sobrecarga: la llamada de función original, verificar el propietario, etc. Averigüemos cuánto.
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;
}
}
}
Llamar a esto con una lista de cuatro direcciones cuesta 109833 de gasolina, lo que resulta en un costo por dirección de solo 27458.25 de gasolina. No lo haremos mucho mejor que eso en una lista blanca explícita, ya que el límite inferior es 20k de gasolina por dirección.
Usando este método, el costo total de éter al precio bajo seguro se ha reducido a aproximadamente:
50k addresses * 27k gas * 0.5 gigawei ~= 0.7 Ether
Alternativas más raras
Tal vez realmente no tenga una lista blanca, tal vez sea una lista negra o una lista de invitados donde los falsos positivos ocasionales no son tan malos. Entonces un filtro de floración podría ser una solución razonable. Ajustar el filtro de floración requiere saber demasiado sobre su caso de uso específico, pero podría reducir fácilmente el costo total en 1,000x o 10,000x.
Hay algunas alternativas más modernas que es esencial considerar ahora que el gas es mucho más alto.
Utilice todas las direcciones de su lista para generar un árbol de Merkle. Luego, pídale al usuario que envíe su prueba (puede servir en el front-end, no confidencial ya que solo el titular de la dirección puede usar su prueba), junto con la llamada de menta.
Considero esta flexibilidad media porque si desea cambiar la lista, debe actualizar la raíz en cadena.
Código de generación (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");
}
Código de verificación (solidez)
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_);
}
Este fragmento usa código de la biblioteca OpenZeppelin Merkle Proof y merkletreejs
Genere firmas con anticipación o en vivo en un back-end y entréguelas al usuario para que las incluya en su llamada de menta. Estos tampoco son sensibles, por lo que se pueden servir en el front-end sin ninguna autenticación.
Código de firma (mecanografiado)
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
}
Verificando código (Solidez)
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"
);
_;
}
Estos fragmentos están adaptados de este repositorio: https://github.com/msfeldstein/EIP712-whitelisting/blob/main/contracts/EIP712Whitelisting.sol
es bastante fácil lo haces así. a través de firmas. necesitas un back-end....
función matchAddressSigner (bytes32 hash, firma de memoria de bytes) vista privada devuelve (bool) { return _signerAddress == hash.recover (firma); }
con
require(matchAddressSigner(hash, sig), "no menta directa");
....
blockchaindotsol
Transaction cost: 20784 gas
, a diferencia delTransaction cost: 43464 gas
, como dijiste. Estoy mirando Estimaciones de gas -> Externo -> whitelistAddress (dirección). ¿Hay otro lugar donde se estima el gas?tallista
tallista
blockchaindotsol
blockchaindotsol
Roca flotante
bobbi bennett