Het onderwerp blockchain blijft niet alleen een bron van allerlei hypes, maar ook van zeer waardevolle ideeën vanuit technologisch oogpunt. Daarom omzeilde ze de inwoners van de zonnige stad niet. Mensen kijken nauwlettend, studeren en proberen hun expertise in de traditionele infobase te verschuiven naar blockchain-systemen. Tot nu toe puntsgewijs: een van de ontwikkelingen van Rostelecom-Solar is in staat om de veiligheid van software te controleren op basis van de blockchain. En gaandeweg ontstaan er enkele gedachten over het oplossen van toegepaste problemen van de blockchain-gemeenschap. Een van deze lifehacks – hoe je het adres van een slim contract kunt bepalen voordat je het gebruikt met CREATE2 – wil ik vandaag met je delen onder de bezuiniging.
De CREATE2-opcode werd op 28 februari van dit jaar toegevoegd aan de hard fork van Constantinopel. Zoals vermeld in het EIP, werd deze opcode voornamelijk geïntroduceerd voor staatskanalen. We hebben het echter gebruikt om een ander probleem op te lossen.
Er zijn gebruikers met saldi op de beurs. We moeten elke gebruiker een Ethereum-adres geven waarnaar iedereen tokens kan sturen, waardoor zijn account wordt aangevuld. Laten we deze adressen "portefeuilles" noemen. Wanneer tokens in portemonnees aankomen, moeten we ze naar een enkele portemonnee (hotwallet) sturen.
In de volgende secties analyseer ik opties om dit probleem op te lossen zonder CREATE2 en leg ik uit waarom we deze hebben verlaten. Als u alleen geïnteresseerd bent in het eindresultaat, kunt u dit vinden in de sectie Eindoplossing.
Ethereum-adressen
De eenvoudigste oplossing is het genereren van nieuwe Ethereum-adressen voor nieuwe gebruikers. Deze adressen zullen de portemonnees zijn. Om tokens van een portemonnee naar hotwallet over te zetten, moet u de transactie ondertekenen door de functie aan te roepen overdracht() met de privésleutel van de portemonnee uit de backend.
Deze aanpak heeft de volgende voordelen:
- het is gewoon
- de kosten voor het overbrengen van tokens van een portemonnee naar hotwallet zijn gelijk aan de kosten van een functieaanroep overdracht()
We hebben deze aanpak echter verlaten omdat deze één belangrijk nadeel heeft: je moet privésleutels ergens opslaan. En het is niet alleen dat ze verloren kunnen gaan, maar ook dat u de toegang tot deze sleutels zorgvuldig moet beheren. Als ten minste één ervan gecompromitteerd is, zullen de tokens van een bepaalde gebruiker de hot wallet niet bereiken.
Maak voor elke gebruiker een afzonderlijk slim contract
Door voor elke gebruiker een afzonderlijk slim contract te implementeren, kunt u geen privésleutels van portemonnees op de server opslaan. De beurs zal dit slimme contract aanroepen om de tokens naar de hotwallet over te dragen.
We hebben ook van deze oplossing afgezien, omdat de gebruiker zijn portemonnee-adres niet kan zien zonder een slim contract in te zetten (dit is eigenlijk mogelijk, maar op een nogal gecompliceerde manier met andere nadelen die we hier niet zullen bespreken). Op de beurs kan de gebruiker zoveel accounts aanmaken als hij nodig heeft, en iedereen heeft zijn eigen portemonnee nodig. Dit betekent dat we geld moeten uitgeven aan het implementeren van een contract zonder er zelfs maar zeker van te zijn dat de gebruiker dit account zal gebruiken.
Opcode CREATE2
Om het probleem van de vorige methode op te lossen, hebben we besloten de CREATE2-opcode te gebruiken. Met CREATE2 kunt u vooraf het adres bepalen waar het slimme contract zal worden ingezet. Het adres wordt berekend met behulp van de volgende formule:
keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:]
, Waar:
- adres — adres van het slimme contract dat CREATE2 zal aanroepen
- zout - willekeurige waarde
- init_code — slimme contractbytecode voor implementatie
Dit zorgt ervoor dat het adres dat wij aan de gebruiker verstrekken ook daadwerkelijk de gewenste bytecode bevat. Bovendien kan dit slimme contract worden ingezet wanneer we maar willen. Bijvoorbeeld wanneer een gebruiker besluit zijn portemonnee voor de eerste keer te gebruiken.
Bovendien kunt u het slimme contractadres elke keer berekenen in plaats van het op te slaan, omdat:
- adres in de formule is constant omdat dit het adres is van onze portefeuillefabriek
- zout — user_id-hash
- init_code is permanent omdat we dezelfde portemonnee gebruiken
Meer verbeteringen
De vorige oplossing heeft nog steeds één nadeel: je moet betalen voor de implementatie van slimme contracten. Je kunt er echter vanaf komen. Hiervoor kun je de functie aanroepen overdracht()en dan zelfvernietiging() in de portemonnee-constructor. En dan wordt het gas voor de inzet van het slimme contract teruggegeven.
In tegenstelling tot wat vaak wordt gedacht, kunt u een slim contract meerdere keren op hetzelfde adres implementeren met de CREATE2-opcode. Dit komt omdat CREATE2 controleert of de nonce van het doeladres nul is (aan het begin van de constructor wordt de waarde "1" toegewezen). Tegelijkertijd is de functie zelfvernietiging() reset elke keer nonce-adressen. Dus als je CREATE2 opnieuw aanroept met dezelfde argumenten, zal de nonce-controle slagen.
Houd er rekening mee dat deze oplossing vergelijkbaar is met de Ethereum-adresoptie, maar zonder de noodzaak om privésleutels op te slaan. De kosten voor het overboeken van geld van een portemonnee naar hotwallet zijn ongeveer gelijk aan de kosten voor het aanroepen van een functie overdracht(), aangezien we niet betalen voor de inzet van het slimme contract.
Laatste beslissing
Oorspronkelijk opgesteld door:
- functie om zout binnen te krijgen user_id
- slim contract dat de CREATE2-opcode aanroept met het juiste zout (dat wil zeggen portemonneefabriek)
- portemonnee bytecode die overeenkomt met het contract met de volgende constructor:
constructor () {
address hotWallet = 0x…;
address token = 0x…;
token.transfer (hotWallet, token.balanceOf (address (this)));
selfdestruct (address (0));
}
Voor elke nieuwe gebruiker tonen wij door middel van een berekening zijn/haar portemonnee-adres
keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:]
Wanneer de gebruiker tokens overdraagt naar het corresponderende portemonnee-adres, ziet onze backend de Transfer-gebeurtenis met de parameter _tot, gelijk aan het portemonneeadres. Op dit moment is het al mogelijk om het saldo van de gebruiker op de beurs te verhogen voordat de portemonnee wordt ingezet.
Wanneer er voldoende tokens zijn verzameld in het portemonnee-adres, kunnen we ze allemaal in één keer overbrengen naar de hotwallet. Om dit te doen, roept de backend de smart contract factory-functie aan, die de volgende acties uitvoert:
function deployWallet (соль uint256) {
bytes memory walletBytecode =…;
// invoke CREATE2 with wallet bytecode and salt
}
Zo wordt de slimme contractconstructor van de portemonnee gebeld, die al zijn tokens naar het hotwallet-adres overbrengt en vervolgens zichzelf vernietigt.
De volledige code is te vinden
Auteur Pavel Kondratenkov, Ethereum-specialist
Bron: www.habr.com