Anatomia contractelor inteligente
Ultima modificare: @nicklcanada(opens in a new tab), 14 iunie 2024
Un contract inteligent este un program care rulează la o adresă pe Ethereum. Este alcătuit din date și funcții care pot fi executate la primirea unei tranzacții. Iată o imagine de ansamblu a ceea ce constituie un contract inteligent.
Condiții prealabile
Aveţi grijă să citiţi mai întâi despre contractele inteligente. Acest document presupune că sunteţi deja familiarizat cu limbaje de programarea precum JavaScript sau Python.
Datele
Toate datele dintr-un contract trebuie alocate unei locații: fie de stocare
, fie de memorie
. Este costisitor de modificat locul de stocare într-un contract inteligent, deci trebuie să hotărâţi unde să vă plasaţi datele.
Stocare
Datele persistente sunt denumite stocare și sunt reprezentate de variabilele de stare. Aceste valori sunt stocate permanent pe blockchain. Trebuie să declaraţi de ce tip sunt, astfel încât contractul să poată ţine socoteala spaţiului de stocare de care are nevoie pe blockchain atunci când compilează.
1// Exemplu Solidity2contract SimpleStorage {3 uint storedData; // Stare variabilă4 // ...5}Copiați
1# Exemplu Vyper2storedData: int128Copiați
Dacă aţi programat deja în limbaje orientate pe obiecte, probabil că știţi deja majoritatea tipurilor. Cu toate acestea, address
(adresa) ar trebui să fie nouă pentru dvs. dacă nu aţi mai dezvoltat pe Ethereum.
Un tip de address
poate conține o adresă Ethereum care echivalează cu 20 de octeţi sau 160 de biți. Acesta răspunde în notație hexazecimală cu un 0x la început.
Alte tipuri includ:
- boolean
- întreg
- numere în virgulă fixă
- matrice de octeți de dimensiuni fixe
- matrice de octeți de dimensiuni dinamice
- literale raționale și întregi
- literale de tip string
- literale hexazecimale
- enum-uri
Pentru mai multe explicații, consultaţi documentele:
Memorie
Valorile care sunt stocate numai pe durata de viață a executării unei funcții contractuale se numesc variabile de memorie. Deoarece acestea nu sunt stocate permanent pe blockchain, sunt mult mai ieftin de utilizat.
Aflaţi mai multe informații despre modul în care EVM stochează datele (stocare, memorie și stivă) în documentația Solidity(opens in a new tab).
Variabile de mediu
În plus față de variabilele pe care le definiţi în contract, există câteva variabile globale speciale. Acestea sunt utilizate în principal pentru a furniza informații despre blockchain sau tranzacția curentă.
Exemple:
Prop | Variabilă de stare | Descriere |
---|---|---|
block.timestamp | uint256 | Marca temporală actuală a epocii blocului |
msg.sender | address | Expeditorul mesajului (apel curent) |
Funcții
În exprimare simplistă, funcțiile pot obține informații sau pot seta informații ca răspuns la tranzacțiile primite.
Există două tipuri de apeluri funcționale:
internal
– acestea nu creează un apel EVM- Funcțiile interne și variabilele de stare pot fi accesate numai intern (adică din contractul actual sau contractele care derivă din acesta)
external
– acestea creează un apel EVM- Funcțiile externe fac parte din interfața contractului, ceea ce înseamnă că pot fi apelate din alte contracte și prin tranzacții. O funcție externă
f
nu poate fi apelată intern (adicăf()
nu funcționează, darthis.f()
funcționează).
- Funcțiile externe fac parte din interfața contractului, ceea ce înseamnă că pot fi apelate din alte contracte și prin tranzacții. O funcție externă
De asemenea, pot fi de tip public
sau private
- funcțiile de tip
public
pot fi apelate intern din cadrul contractului sau extern prin mesaje - funcțiile
private
sunt vizibile numai pentru contractul în care sunt definite și nu sunt vizibile în contractele derivate
Atât funcțiile, cât și variabilele de stare pot fi făcute publice sau private
Iată o funcție pentru actualizarea unei variabile de stare pe un contract:
1// Exemplu Solidity2function update_name(string value) public {3 dapp_name = value;4}Copiați
- Parametrul
value
de tipstring
este trecut în funcția:update_name
- Este declarat
public
, ceea ce înseamnă că oricine îl poate accesa - Nu este declarat
view
, astfel încât să poată modifica starea contractului
Funcții de vizualizare
Aceste funcții promit să nu modifice starea datelor contractului. Exemplele obişnuite sunt funcțiile „getter” (de obținere) – s-ar putea să utilizaţi acest lucru pentru a primi soldul unui utilizator, de exemplu.
1// Exemplu Solidity2function balanceOf(address _owner) public view returns (uint256 _balance) {3 return ownerPizzaCount[_owner];4}Copiați
1dappName: public(string)23@view4@public5def readName() -> string:6 return dappNameCopiați
Ce se consideră ca modificator de stare:
- Scrierea în variabilele de stare.
- Emiterea de evenimente(opens in a new tab).
- Crearea altor contracte(opens in a new tab).
- Folosirea
selfdestruct
. - Trimiterea de ether prin apeluri.
- Apelarea oricărei funcții care nu este marcată
view
saupure
. - Folosirea de apeluri de nivel inferior.
- Utilizarea ansamblului în linie care conține anumite opcoduri.
Funcții constructor
Funcțiile constructor
sunt executate o singură dată, la prima implementare a contractului. Precum constructor
-ul din multe limbaje de programare bazate pe clase, aceste funcții inițializează adesea variabilele de stare la valorile lor specificate.
1// Exemplu Solidity2// Inițializează datele contractului, setând `proprietarul`3// la adresa creatorului contractului.4constructor() public {5 // Toate contractele inteligente se bazează pe tranzacții externe pentru a le declanșa funcțiile.6 // `msg`este o variabilă globală care include date relevante privind tranzacția dată,7 // cum ar fi adresa expeditorului și valoarea ETH inclusă în tranzacție.8 // Află mai multe: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties9 owner = msg.sender;10}Afișează totCopiați
1# exemplu Vyper23@external4def __init__(_beneficiary: address, _bidding_time: uint256):5 self.beneficiary = _beneficiary6 self.auctionStart = block.timestamp7 self.auctionEnd = self.auctionStart + _bidding_timeCopiați
Funcții încorporate
În plus față de variabilele și funcțiile pe care le definiţi în contract, există câteva funcții speciale încorporate. Cel mai evident exemplu este:
address.send()
– Soliditysend(address)
– Vyper
Acestea permit contractelor să trimită ETH către alte conturi.
Scrierea de funcții
Funcția dvs. are nevoie de:
- parametru variabil și tipul acestuia (dacă acceptă parametri)
- declararea dacă este internal/external
- declararea dacă este pure/view/payable
- tipul de răspuns (dacă răspunde printr-o valoare)
1pragma solidity >=0.4.0 <=0.6.0;23contract ExampleDapp {4 string dapp_name; // state variable56 // Called when the contract is deployed and initializes the value7 constructor() public {8 dapp_name = "My Example dapp";9 }1011 // Get Function12 function read_name() public view returns(string) {13 return dapp_name;14 }1516 // Set Function17 function update_name(string value) public {18 dapp_name = value;19 }20}Afișează totCopiați
Un contract complet ar putea arăta astfel. Aici funcția constructor
furnizează o valoare inițială pentru variabila dapp_name
.
Evenimente și jurnale
Evenimentele vă permit să comunicaţi cu contractul dvs. inteligent din frontend sau din alte aplicații cu abonare. Când o tranzacție este minată, contractele inteligente pot emite evenimente și pot scrie jurnale în blockchain, pe care frontend-ul le poate procesa.
Exemple adnotate
Acestea sunt câteva exemple scrise în Solidity. Dacă doriţi să vă jucaţi cu codul, puteţi interacționa cu el în Remix(opens in a new tab).
Hello world
1// Specifică versiunea Solidity, utilizând versiuni semantice.2// Află mai multe: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma3pragma solidity ^0.5.10;45// Definește un contract numit `HelloWorld`.6// Un contract este o colecție de funcții și date - (starea sa).7// Odată implementat, un contract se află la o anumită adresă din blockchain-ul Ethereum.8// Află mai multe: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html9contract HelloWorld {1011 // Declară o variabilă de stare `message` de tip `string`.12 // Variabilele de stare sunt variabile ale căror valori sunt stocate permanent în stocarea contractului.13 // Cuvântul cheie `public` face variabile accesibile din afara unui contract14 // și creează o funcție pe care alte contracte sau clienți o pot apela pentru a accesa valoarea.15 string public message;1617 // Similar cu multe limbaje orientate pe obiecte bazate pe clase, un constructor este18 // o funcție specială care se execută numai la crearea contractului.19 // Funcțiile constructor sunt utilizate pentru a inițializa datele contractului.20 // Află mai multe: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors21 constructor(string memory initMessage) public {22 // Acceptă un argument de string `initMessage` și setează valoarea23 // în variabila de stocare `message` a contractului).24 message = initMessage;25 }2627 // O funcție publică care acceptă un argument string28 // și actualizează variabila de stocare `message`.29 function update(string memory newMessage) public {30 message = newMessage;31 }32}Afișează totCopiați
Token
1pragma solidity ^0.5.10;23contract Token {4 // `address` este comparabilă cu o adresă de e-mail - este utilizată pentru a identifica un cont pe Ethereum.5 // Adresele pot reprezenta un contract inteligent sau un cont extern (utilizator).6 // Află mai multe: https://solidity.readthedocs.io/en/v0.5.10/types.html#address7 address public owner;89 // `mapping` este în esență o structură de date de tabel hash.10 // Acest `mapping` atribuie un număr întreg nesemnat (echilibrul tokenului) unei adrese (deținătorul tokenului).11 // Află mai multe: https://solidity.readthedocs.io/en/v0.5.10/types.html#mapping-types12 mapping (address => uint) public balances;1314 // Evenimentele permit înregistrarea activității pe blockchain.15 // Clienții Ethereum pot asculta evenimente pentru a reacționa la modificările stării contractului.16 // Află mai multe: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#events17 event Transfer(address from, address to, uint amount);1819 // Inițializează datele contractului, setând `owner`20 // la adresa creatorului contractului.21 constructor() public {22 // Toate contractele inteligente se bazează pe tranzacții externe pentru a declanșa funcțiile sale.23 // `msg` este o variabilă globală care include date relevante despre tranzacția dată,24 // cum ar fi adresa expeditorului și valoarea ETH inclusă în tranzacție.25 // Află mai multe: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties26 owner = msg.sender;27 }2829 // Creează o cantitate de tokenuri noi și le trimite la o adresă.30 function mint(address receiver, uint amount) public {31 // `require` este o structură de control utilizată pentru a impune anumite condiții.32 // Dacă o declarație `require` este evaluată ca `false`, se declanșează o excepție,33 // care întoarce toate modificările aduse stării în timpul apelului curent.34 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions3536 // Only the contract owner can call this function37 require(msg.sender == owner, "You are not the owner.");3839 // Enforces a maximum amount of tokens40 require(amount < 1e60, "Maximum issuance exceeded");4142 // Increases the balance of `receiver` by `amount`43 balances[receiver] += amount;44 }4546 // Sends an amount of existing tokens from any caller to an address.47 function transfer(address receiver, uint amount) public {48 // Expeditorul trebuie să aibă suficiente tokenuri pentru a le trimite49 require(amount <= balances[msg.sender], "Insufficient balance.");5051 // Reglează soldurile token ale celor două adrese52 balances[msg.sender] -= amount;53 balances[receiver] += amount;5455 // Emite evenimentul definit anterior56 emit Transfer(msg.sender, receiver, amount);57 }58}Afișează totCopiați
Activ digital unic
1pragma solidity ^0.5.10;23// Importă simboluri din alte fișiere în contractul curent.4// În acest caz, o serie de contracte de ajutor de la OpenZeppelin.5//Află mai multe: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#importing-other-source-files67import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol";8import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";9import "../node_modules/@openzeppelin/contracts/introspection/ERC165.sol";10import "../node_modules/@openzeppelin/contracts/math/SafeMath.sol";1112// Cuvântul cheie `is` este folosit pentru a moșteni funcții și cuvinte cheie din contracte externe.13// În acest caz, `CryptoPizza` moștenește din contractele `IERC721` și `ERC165`.14// Află mai multe: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#inheritance15contract CryptoPizza is IERC721, ERC165 {16 // Folosește biblioteca OpenZeppelin's SafeMath ca să efectuezi operațiuni aritmetice în siguranță.17 // Află mai multe: https://docs.openzeppelin.com/contracts/2.x/api/math#SafeMath18 using SafeMath for uint256;1920 // Variabilele de stare constantă din Solidity sunt similare cu alte limbaje21 // dar trebuie să atribui dintr-o expresie care este constantă în timpul compilării.22 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constant-state-variables23 uint256 constant dnaDigits = 10;24 uint256 constant dnaModulus = 10 ** dnaDigits;25 bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;2627 // Struct types let you define your own type28 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#structs29 struct Pizza {30 string name;31 uint256 dna;32 }3334 // Creates an empty array of Pizza structs35 Pizza[] public pizzas;3637 // Mapping from pizza ID to its owner's address38 mapping(uint256 => address) public pizzaToOwner;3940 // Mapping from owner's address to number of owned token41 mapping(address => uint256) public ownerPizzaCount;4243 // Mapping from token ID to approved address44 mapping(uint256 => address) pizzaApprovals;4546 // You can nest mappings, this example maps owner to operator approvals47 mapping(address => mapping(address => bool)) private operatorApprovals;4849 // Internal function to create a random Pizza from string (name) and DNA50 function _createPizza(string memory _name, uint256 _dna)51 // The `internal` keyword means this function is only visible52 // within this contract and contracts that derive this contract53 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#visibility-and-getters54 internal55 // `isUnique` is a function modifier that checks if the pizza already exists56 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html#function-modifiers57 isUnique(_name, _dna)58 {59 // Adds Pizza to array of Pizzas and get id60 uint256 id = SafeMath.sub(pizzas.push(Pizza(_name, _dna)), 1);6162 // Checks that Pizza owner is the same as current user63 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions6465 // note that address(0) is the zero address,66 // indicating that pizza[id] is not yet allocated to a particular user.6768 assert(pizzaToOwner[id] == address(0));6970 // Maps the Pizza to the owner71 pizzaToOwner[id] = msg.sender;72 ownerPizzaCount[msg.sender] = SafeMath.add(73 ownerPizzaCount[msg.sender],74 175 );76 }7778 // Creates a random Pizza from string (name)79 function createRandomPizza(string memory _name) public {80 uint256 randDna = generateRandomDna(_name, msg.sender);81 _createPizza(_name, randDna);82 }8384 // Generates random DNA from string (name) and address of the owner (creator)85 function generateRandomDna(string memory _str, address _owner)86 public87 // Functions marked as `pure` promise not to read from or modify the state88 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#pure-functions89 pure90 returns (uint256)91 {92 // Generates random uint from string (name) + address (owner)93 uint256 rand = uint256(keccak256(abi.encodePacked(_str))) +94 uint256(_owner);95 rand = rand % dnaModulus;96 return rand;97 }9899 // Returns array of Pizzas found by owner100 function getPizzasByOwner(address _owner)101 public102 // Functions marked as `view` promise not to modify state103 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#view-functions104 view105 returns (uint256[] memory)106 {107 // Uses the `memory` storage location to store values only for the108 // lifecycle of this function call.109 // Află mai multe: https://solidity.readthedocs.io/en/v0.5.10/introduction-to-smart-contracts.html#storage-memory-and-the-stack110 uint256[] memory result = new uint256[](ownerPizzaCount[_owner]);111 uint256 counter = 0;112 for (uint256 i = 0; i < pizzas.length; i++) {113 if (pizzaToOwner[i] == _owner) {114 result[counter] = i;115 counter++;116 }117 }118 return result;119 }120121 // Transferă Pizza și proprietatea asupra altei adrese122 function transferFrom(address _from, address _to, uint256 _pizzaId) public {123 require(_from != address(0) && _to != address(0), "Adresă invalidă.");124 require(_exists(_pizzaId), "Pizza nu există.");125 require(_from != _to, "Nu se poate transfera la aceeași adresă.");126 require(_isApprovedOrOwner(msg.sender, _pizzaId), "Adresa nu este aprobată.");127128 ownerPizzaCount[_to] = SafeMath.add(ownerPizzaCount[_to], 1);129 ownerPizzaCount[_from] = SafeMath.sub(ownerPizzaCount[_from], 1);130 pizzaToOwner[_pizzaId] = _to;131132 //Emite eveniment definit în contractul IERC721 importat133 emit Transfer(_from, _to, _pizzaId);134 _clearApproval(_to, _pizzaId);135 }136137 /**138 * Transferă în siguranță dreptul de proprietate asupra unui ID de token dat la o altă adresă139 * Dacă adresa țintă este un contract, aceasta trebuie să implementeze `onERC721Received`,140 * care este apelat la un transfer sigur și returnează valoarea magică141 * `bytes4 (keccak256 (" onERC721Received(address,address,uint256, bytes)"))`;142 * în caz contrar, transferul este anulat.143 */144 function safeTransferFrom(address from, address to, uint256 pizzaId)145 public146 {147 // solium-disable-next-line arg-overflow148 this.safeTransferFrom(from, to, pizzaId, "");149 }150151 /**152 * Transferă în siguranță dreptul de proprietate asupra unui ID de token dat la o altă adresă153 * Dacă adresa țintă este un contract, aceasta trebuie să implementeze `onERC721Received`,154 * care este apelat la un transfer sigur și returnează valoarea magică155 * `bytes4 (keccak256 (" onERC721Received (address, address, uint256, bytes) "))`;156 * în caz contrar, transferul este anulat.157 */158 function safeTransferFrom(159 address from,160 address to,161 uint256 pizzaId,162 bytes memory _data163 ) public {164 this.transferFrom(from, to, pizzaId);165 require(_checkOnERC721Received(from, to, pizzaId, _data), "Must implement onERC721Received.");166 }167168 /**169 * Internal function to invoke `onERC721Received` on a target address170 * The call is not executed if the target address is not a contract171 */172 function _checkOnERC721Received(173 address from,174 address to,175 uint256 pizzaId,176 bytes memory _data177 ) internal returns (bool) {178 if (!isContract(to)) {179 return true;180 }181182 bytes4 retval = IERC721Receiver(to).onERC721Received(183 msg.sender,184 from,185 pizzaId,186 _data187 );188 return (retval == _ERC721_RECEIVED);189 }190191 // Burns a Pizza - destroys Token completely192 // The `external` function modifier means this function is193 // part of the contract interface and other contracts can call it194 function burn(uint256 _pizzaId) external {195 require(msg.sender != address(0), "Invalid address.");196 require(_exists(_pizzaId), "Pizza does not exist.");197 require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved.");198199 ownerPizzaCount[msg.sender] = SafeMath.sub(200 ownerPizzaCount[msg.sender],201 1202 );203 pizzaToOwner[_pizzaId] = address(0);204 }205206 // Returns count of Pizzas by address207 function balanceOf(address _owner) public view returns (uint256 _balance) {208 return ownerPizzaCount[_owner];209 }210211 // Returns owner of the Pizza found by id212 function ownerOf(uint256 _pizzaId) public view returns (address _owner) {213 address owner = pizzaToOwner[_pizzaId];214 require(owner != address(0), "Invalid Pizza ID.");215 return owner;216 }217218 // Approves other address to transfer ownership of Pizza219 function approve(address _to, uint256 _pizzaId) public {220 require(msg.sender == pizzaToOwner[_pizzaId], "Must be the Pizza owner.");221 pizzaApprovals[_pizzaId] = _to;222 emit Approval(msg.sender, _to, _pizzaId);223 }224225 // Returns approved address for specific Pizza226 function getApproved(uint256 _pizzaId)227 public228 view229 returns (address operator)230 {231 require(_exists(_pizzaId), "Pizza does not exist.");232 return pizzaApprovals[_pizzaId];233 }234235 /**236 * Private function to clear current approval of a given token ID237 * Reverts if the given address is not indeed the owner of the token238 */239 function _clearApproval(address owner, uint256 _pizzaId) private {240 require(pizzaToOwner[_pizzaId] == owner, "Must be pizza owner.");241 require(_exists(_pizzaId), "Pizza does not exist.");242 if (pizzaApprovals[_pizzaId] != address(0)) {243 pizzaApprovals[_pizzaId] = address(0);244 }245 }246247 /*248 * Sets or unsets the approval of a given operator249 * An operator is allowed to transfer all tokens of the sender on their behalf250 */251 function setApprovalForAll(address to, bool approved) public {252 require(to != msg.sender, "Cannot approve own address");253 operatorApprovals[msg.sender][to] = approved;254 emit ApprovalForAll(msg.sender, to, approved);255 }256257 // Tells whether an operator is approved by a given owner258 function isApprovedForAll(address owner, address operator)259 public260 view261 returns (bool)262 {263 return operatorApprovals[owner][operator];264 }265266 // Takes ownership of Pizza - only for approved users267 function takeOwnership(uint256 _pizzaId) public {268 require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved.");269 address owner = this.ownerOf(_pizzaId);270 this.transferFrom(owner, msg.sender, _pizzaId);271 }272273 // Checks if Pizza exists274 function _exists(uint256 pizzaId) internal view returns (bool) {275 address owner = pizzaToOwner[pizzaId];276 return owner != address(0);277 }278279 // Checks if address is owner or is approved to transfer Pizza280 function _isApprovedOrOwner(address spender, uint256 pizzaId)281 internal282 view283 returns (bool)284 {285 address owner = pizzaToOwner[pizzaId];286 // Disable solium check because of287 // https://github.com/duaraghav8/Solium/issues/175288 // solium-disable-next-line operator-whitespace289 return (spender == owner ||290 this.getApproved(pizzaId) == spender ||291 this.isApprovedForAll(owner, spender));292 }293294 // Check if Pizza is unique and doesn't exist yet295 modifier isUnique(string memory _name, uint256 _dna) {296 bool result = true;297 for (uint256 i = 0; i < pizzas.length; i++) {298 if (299 keccak256(abi.encodePacked(pizzas[i].name)) ==300 keccak256(abi.encodePacked(_name)) &&301 pizzas[i].dna == _dna302 ) {303 result = false;304 }305 }306 require(result, "Pizza with such name already exists.");307 _;308 }309310 // Returns whether the target address is a contract311 function isContract(address account) internal view returns (bool) {312 uint256 size;313 // Currently there is no better way to check if there is a contract in an address314 // than to check the size of the code at that address.315 // Consultă https://ethereum.stackexchange.com/a/14016/36603316 // pentru mai multe detalii despre cum funcționează acest lucru.317 // DE_FĂCUT Verifică din nou acest lucru înainte de lansarea Serenity, deoarece toate adresele vor fi318 // contracte atunci.319 // solium-disable-next-line security/no-inline-assembly320 assembly {321 size := extcodesize(account)322 }323 return size > 0;324 }325}Afișează totCopiați
Referințe suplimentare
Consultaţi documentația Solidity și Vyper pentru a vedea o prezentare mai completă a contractelor inteligente:
Subiecte corelate
Tutoriale corelate
- Reducerea contractelor pentru a contracara limita de mărime a contractului – Câteva sfaturi practice pentru reducerea dimensiunii contractului dvs. inteligent.
- Înregistrarea datelor din contracte inteligente cu evenimente – O introducere despre evenimentele contractelor inteligente și cum le puteţi utiliza pentru a înregistra date.
- Interacționaţi cu alte contracte din Solidity – Cum să implementaţi un contract inteligent dintr-un contract existent și să interacționaţi cu acesta.