Ajude a atualizar esta página

🌏

Há uma nova versão desta página mas, no momento, ela está apenas em inglês. Ajude-nos a traduzir a última versão.

Nenhum bug encontrado!🐛

Por enquanto, esta página permanecerá em inglês.

Anatomia dos contratos inteligentes

Última edição: , Invalid DateTime
Editar Página

Um smart contract é um programa executado em um endereço no Ethereum. Eles são compostos por dados e funções que podem ser executadas ao receber uma transação. Aqui vai uma visão geral do que compõe um contrato inteligente.

Pré-requisitos

É recomendável antes ler sobre contratos inteligentes. Este documento presume que você já está familiarizado com linguagens de programação como JavaScript ou Python.

Dados

Quaisquer dados de contrato devem ser atribuídos a um local: seja para armazenamento ou memória. É caro modificar o armazenamento em um contrato inteligente, então você precisa considerar onde seus dados devem estar no ar.

Armazenamento

Dados persistentes são referidos como armazenamento e são representados por variáveis de estado. Esses valores são armazenados permanentemente na blockchain. Você precisa declarar o tipo para que o contrato possa manter um registro de quanto espaço na cadeia de blocos será necessário quando ele compilar.

1// Exemplo Solidity
2contract SimpleStorage {
3 uint storedData; // State variable
4 // ...
5}
6
📋 Copiar
1# Exemplo Vyper
2storedData: int128
3
📋 Copiar

Se você já programou linguagens orientadas a objetos, provavelmente você estará familiarizado com a maioria dos tipos. No entanto, address (endereço) deve ser novo para você se você for novo no desenvolvimento com Ethereum.

Um tipo address pode conter um endereço Ethereum que equivale a 20 bytes ou 160 bits. Ele retorna em hexadecimal com um 0 à frente.

Outros tipos incluem:

  • booleano
  • inteiro
  • números de ponto fixo
  • arrays de bytes de tamanho fixo
  • arrays de bytes de tamanho dinâmico
  • Literais racionais e inteiros
  • String literals
  • Hexadecimais literais
  • Enumeradores

Para mais explicação, dê uma olhada na documentação:

Memória

Valores que são armazenados apenas para a duração da execução da função de contratos são chamadas de variáveis de memória. Como estas não são armazenadas permanentemente na cadeia de blocos, são muito mais baratas de usar.

Saiba mais sobre como o EVM armazena dados (Storage, Memória e Stack) em Solidity docs.

Variáveis de ambiente

Além das variáveis definidas no seu contrato, existem algumas variáveis globais especiais. Elas são usadas principalmente para fornecer informações sobre a cadeia de blocos ou transação atual.

Exemplos:

PropVariável de estadoDescrição
block.timestampuint256Data/hora de início do bloco atual
msg.senderendereçoRemetente da mensagem (chamada atual)

Funções

Da forma mais simplista, funções podem obter informação ou um conjunto de informações em resposta a entrada de transações.

Existem dois tipos de chamadas de função:

  • internal - estas não criam uma chamada EVM
    • Funções internas e variáveis de estado só podem ser acessadas internamente (ou seja, de dentro do contrato atual ou de contratos derivados do mesmo)
  • external - estas criam uma chamada EVM
    • Funções externas fazem parte da interface do contrato, o que significa que elas podem ser chamadas a partir de outros contratos e através de transações. Uma função externa f não pode ser chamada internamente (ou seja, f() não funciona, mas this.f() funciona).

Também podem ser públicas ou privadas

  • funções públicas podem ser chamadas internamente a partir de dentro do contrato ou externamente por meio de mensagens
  • funções privadas são visíveis apenas para o contrato no qual elas estão definidas e não nos contratos derivados

Tanto funções quanto variáveis de estado podem ser tornadas públicas ou privadas

Aqui está uma função para atualizar uma variável de estado em um contrato:

1// Exemplo de Solidity
2function update_name(string value) public {
3 dapp_name = value;
4}
5
📋 Copiar
  • O parâmetro valor do tipo string é passado para a função: update_name
  • É declarado público, o que significa que qualquer um pode acessá-lo
  • Não é declarada a visão, então ela pode modificar o estado do contrato

Ver funções

Essas funções prometem não modificar o estado dos dados do contrato. Exemplos comuns são funções "obter" – você pode usar isso para receber o saldo de um usuário, por exemplo.

1// Exemplo
2function balanceOf(address _owner) public view return (uint256 _balance) {
3 return ownerPizzaCount[_owner];
4}
5
📋 Copiar
1dappName: public(string)
2
3@view
4@public
5def readName() -> string:
6 return dappName
7
📋 Copiar

O que é considerado como modificar estado:

  1. Escrevendo variáveis de estado.
  2. Emitir eventos.
  3. Criação de outros contratos.
  4. Usando autodestruct.
  5. Enviando ether por chamadas.
  6. Chamar qualquer função não marcada comoviewoupuro.
  7. Usando chamadas de baixo nível.
  8. Usando montagem em linha que contém certos códigos.

Funções construtor

construtor funções são executadas apenas uma vez quando o contrato é implantado pela primeira vez. Como o construtor em muitas linguagens de programação baseadas em classe, essas funções geralmente inicializam variáveis de estado para seus valores especificados.

1// Exemplo Solidity
2// Inicializa os dados do contrato, definindo o `owner`
3// como endereço do criador do contrato.
4constructor() public {
5 // Todos os contratos inteligentes dependem de transações externas para acionar suas funções.
6 // `msg` é uma variável global que inclui dados relevantes sobre a transação em questão,
7 // como o endereço do remetente e o valor ETH incluído na transação.
8 // Saiba mais: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties
9 owner = msg.sender;
10}
11
Exibir tudo
📋 Copiar
1# Exemplo Vyper
2
3@external
4def __init__(_beneficiary: endereço, _bidding_time: uint256):
5 mesmo. eneficiário = _beneficiário
6 self.auctionStart = block.timestamp
7 self.auctionEnd = self.auctionStart + _bidding_time
8
📋 Copiar

Funções integradas

Além das variáveis definidas no seu contrato, existem algumas variáveis globais especiais. O exemplo mais óbvio é:

  • address.send() – Solidity
  • Enviar(endereço) – Vyper

Estes permitem contratos para enviar ETH para outras contas.

Funções utilitárias

Sua função precisa:

  • variável e tipo de parâmetro (se aceitar parâmetros)
  • declaração de interno/externo
  • declaração de puro/visualização/pagável
  • tipo de retorno (se ele retornar um valor)
1pragma solidity >=0.4.0 <=0.6.0;
2
3contract ExampleDapp {
4 string dapp_name; // state variable
5
6 // Called when the contract is deployed and initializes the value
7 constructor() public {
8 dapp_name = "My Example dapp";
9 }
10
11 // Get Function
12 function read_name() public view returns(string) {
13 return dapp_name;
14 }
15
16 // Set Function
17 function update_name(string value) public {
18 dapp_name = value;
19 }
20}
21
Exibir tudo
📋 Copiar

Um contrato completo pode parecer algo assim. Aqui a função construtor fornece um valor inicial para a variável dapp_name.

Eventos e registros

Eventos permitem que você se comunique com seu contrato inteligente na interface do seu site ou de outros aplicativos de assinatura. Quando uma transação é minerada, os contratos inteligentes podem emitir eventos e escrever registros na cadeia de blocos que o frontend pode então processar.

Exemplos anotados

Estes são alguns exemplos escritos em Solidity. Se você quiser brincar com o código, pode interagir com eles no Remix.

Olá, mundo

1// Especifica a versão do Solidity usando a versão semântica.
2// Saiba mais: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
3pragma solidity ^0.5.10;
4
5// Define um contrato chamado `HelloWorld`.
6// Um contrato é uma coleção de funções e dados (seu estado).
7// Uma vez implantado, um contrato reside em um endereço específico na blockchain Ethereum.
8// Saiba mais: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
9contract HelloWorld {
10
11 // Declita uma variável `message` de tipo `string`.
12 // Variáveis de estado são variáveis cujos valores são permanentemente armazenados no armazenamento do contrato.
13 // A palavra-chave 'público' torna variáveis acessíveis fora de um contrato
14 // e cria uma função que outros contratos ou clientes podem chamar para acessar o valor.
15 mensagem pública de cadeia;
16
17 // Semelhante a muitas linguagens de objeto, baseadas em classes, um construtor é
18 // uma função especial que é executada somente após a criação do contrato.
19 // Os construtores são usados para inicializar os dados do contrato.
20 // Saiba mais: https://solidity.readthedocs.io/en/v0.5.10/contracts. tml#constructors
21 constructor(string memory initMessage) public {
22 // Aceita um argumento de string `initMessage` e define o valor
23 // na variável de armazenamento `message` do contrato).
24 message = initMessage;
25 }
26
27 // Uma função pública que aceita um argumento de string
28 // e atualiza a variável de armazenamento `message`.
29 function update(string memory newMessage) public {
30 message = newMessage;
31 }
32}
33
Exibir tudo
📋 Copiar

Token

1pragma solidity ^0.5.10;
2
3contract Token {
4 // Um "endereço" é comparável a um endereço de e-mail - é usado para comparar uma conta no Ethereum.
5 // Endereços podem representar uma conta de contrato inteligente ou uma conta externa (usuário).
6 // Saiba mais: https://solidity.readthedocs.io/en/v0.5.10/types.html#address
7 address public owner;
8
9 // Um `mapping` é essencialmente uma estrutura de dados de tabela de hash.
10 // Este `mapeamento` atribui um inteiro não assinado (o saldo do token) a um endereço (o titular do token).
11 // Saiba mais: https://solidity.readthedocs.io/en/v0.5.10/types.html#mapping-types
12 mapping (address => uint) public balances;
13
14 // Eventos permitem registro de atividade no blockchain.
15 // Clientes Ethereum podem ouvir eventos para reagir às alterações do estado do contrato.
16 // Saiba mais: https://solidity.readthedocs.io/en/v0.5.10/contracts. tml#eventos
17 event Transfer(address from, address to, uint amount);
18
19 // Inicializa os dados do contrato definindo o `owner`
20 // para o endereço do criador do contrato.
21 constructor() public {
22 // Todos os contratos inteligentes dependem de transações externas para acionar suas funções.
23 // `msg` é uma variável global que inclui dados relevantes sobre a transação em questão,
24 // como o endereço do remetente e o valor ETH incluído na transação.
25 // Saiba mais: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties
26 owner = msg.sender;
27 }
28
29 // Cria uma quantidade de novos tokens e os envia para um endereço.
30 function mint(address receiver, uint amount) public {
31 // `require` é uma estrutura de controle usada para aplicar certas condições.
32 // Se um comando `require` for avaliado como `false`, uma exceção é acionada,
33 // que reverte todas as alterações feitas ao estado durante a chamada atual.
34 // Saiba mais: https://solidity.readthedocs.io/en/v0.5.10/control-structures. tml#error-handling-assert-require-revert-and-exceptions
35
36 // Somente o proprietário do contrato pode chamar esta função
37 require(msg. remetente == dono, "Você não é o dono. );
38
39 // Reforça uma quantidade máxima de tokens
40 require(amount < 1e60, "Emissão máxima excedida");
41
42 // Aumenta o saldo de `receiver` em `amount`
43 saldos[receiver] += amount;
44 }
45
46 // Envia uma quantidade de tokens existentes de qualquer chamada para um endereço.
47 function transfer(address receiver, uint amount) public {
48 // O remetente deve ter tokens suficientes para enviar
49 require(amount <= balances[msg.sender], "Insufficient balance.");
50
51 // Ajusta os saldos do token dos dois endereços
52 balances[msg.sender] -= quantidade;
53 balances[receiver] += quantidade;
54
55 // Emite um evendo definido anteriormente
56 emite Transfer(msg.sender, receiver, amount);
57 }
58}
59
Exibir tudo
📋 Copiar

Asset digital único

1pragma solidity ^0.5.10.
2// Neste caso, uma série de contratos auxiliares de OpenZeppelin.
3// Saiba mais: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#importing-other-source-files
4
5import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol";
6import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721Receiver. ol";
7import "../node_modules/@openzeppelin/contracts/introspection/ERC165.sol";
8import "../node_modules/@openzeppelin/contracts/math/SafeMath.sol";
9
10// A palavra-chave `is` é usada para herdar funções e palavras-chave de contratos externos.
11// Neste caso, o `CryptoPizza` herda dos contratos `IERC721` e `ERC165`.
12// Saiba mais: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#inheritance
13contract CryptoPizza is IERC721, ERC165 {
14 // Usa a biblioteca OpenZeppelin para executar operações aritméticas de forma segura.
15 // Saiba mais: https://docs.openzeppelin.com/contracts/2. /api/math#SafeMath
16 usando SafeMath para uint256;
17
18 // Variáveis de estado constantes em Solidity são semelhantes a outros idiomas
19 // mas você deve atribuir a partir de uma expressão que é constante na hora de compilação.
20 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constant-state-variables
21 uint256 constant dnaDigits = 10;
22 uint256 constant dnaModulus = 10 ** dnaDigits;
23 bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;
24
25 // Struct types let you define your own type
26 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#structs
27 struct Pizza {
28 string name;
29 uint256 dna;
30 }
31
32 // Creates an empty array of Pizza structs
33 Pizza[] public pizzas;
34
35 // Mapping from pizza ID to its owner's address
36 mapping(uint256 => address) public pizzaToOwner;
37
38 // Mapping from owner's address to number of owned token
39 mapping(address => uint256) public ownerPizzaCount;
40
41 // Mapping from token ID to approved address
42 mapping(uint256 => address) pizzaApprovals;
43
44 // You can nest mappings, this example maps owner to operator approvals
45 mapping(address => mapping(address => bool)) private operatorApprovals;
46
47 // Internal function to create a random Pizza from string (name) and DNA
48 function _createPizza(string memory _name, uint256 _dna)
49 // The `internal` keyword means this function is only visible
50 // within this contract and contracts that derive this contract
51 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#visibility-and-getters
52 internal
53 // `isUnique` is a function modifier that checks if the pizza already exists
54 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html#function-modifiers
55 isUnique(_name, _dna)
56 {
57 // Adds Pizza to array of Pizzas and get id
58 uint256 id = SafeMath.sub(pizzas.push(Pizza(_name, _dna)), 1);
59
60 // Checks that Pizza owner is the same as current user
61 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions
62
63 // note that address(0) is the zero address,
64 // indicating that pizza[id] is not yet allocated to a particular user.
65
66 assert(pizzaToOwner[id] == address(0));
67
68 // Maps the Pizza to the owner
69 pizzaToOwner[id] = msg.sender;
70 ownerPizzaCount[msg.sender] = SafeMath.add(
71 ownerPizzaCount[msg.sender],
72 1
73 );
74 }
75
76 // Creates a random Pizza from string (name)
77 function createRandomPizza(string memory _name) public {
78 uint256 randDna = generateRandomDna(_name, msg.sender);
79 _createPizza(_name, randDna);
80 }
81
82 // Generates random DNA from string (name) and address of the owner (creator)
83 function generateRandomDna(string memory _str, address _owner)
84 public
85 // Functions marked as `pure` promise not to read from or modify the state
86 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#pure-functions
87 pure
88 returns (uint256)
89 {
90 // Generates random uint from string (name) + address (owner)
91 uint256 rand = uint256(keccak256(abi.encodePacked(_str))) +
92 uint256(_owner);
93 rand = rand % dnaModulus;
94 return rand;
95 }
96
97 // Returns array of Pizzas found by owner
98 function getPizzasByOwner(address _owner)
99 public
100 // Functions marked as `view` promise not to modify state
101 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#view-functions
102 view
103 returns (uint256[] memory)
104 {
105 // Uses the `memory` storage location to store values only for the
106 // lifecycle of this function call.
107 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/introduction-to-smart-contracts.html#storage-memory-and-the-stack
108 uint256[] memory result = new uint256[](ownerPizzaCount[_owner]);
109 uint256 counter = 0;
110 for (uint256 i = 0; i < pizzas.length; i++) {
111 if (pizzaToOwner[i] == _owner) {
112 result[counter] = i;
113 counter++;
114 }
115 }
116 return result;
117 }
118
119 // Transfers Pizza and ownership to other address
120 function transferFrom(address _from, address _to, uint256 _pizzaId) public {
121 require(_from != address(0) && _to != address(0), "Invalid address.");
122 require(_exists(_pizzaId), "Pizza does not exist.");
123 require(_from != _to, "Cannot transfer to the same address.");
124 require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved.");
125
126 ownerPizzaCount[_to] = SafeMath.add(ownerPizzaCount[_to], 1);
127 ownerPizzaCount[_from] = SafeMath.sub(ownerPizzaCount[_from], 1);
128 pizzaToOwner[_pizzaId] = _to;
129
130 // Emits event defined in the imported IERC721 contract
131 emit Transfer(_from, _to, _pizzaId);
132 _clearApproval(_to, _pizzaId);
133 }
134
135 /**
136 * Safely transfers the ownership of a given token ID to another address
137 * If the target address is a contract, it must implement `onERC721Received`,
138 * which is called upon a safe transfer, and return the magic value
139 * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`;
140 * otherwise, the transfer is reverted.
141 */
142 function safeTransferFrom(address from, address to, uint256 pizzaId)
143 public
144 {
145 // solium-disable-next-line arg-overflow
146 this.safeTransferFrom(from, to, pizzaId, "");
147 }
148
149 /**
150 * Safely transfers the ownership of a given token ID to another address
151 * If the target address is a contract, it must implement `onERC721Received`,
152 * which is called upon a safe transfer, and return the magic value
153 * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`;
154 * otherwise, the transfer is reverted.
155 */
156 function safeTransferFrom(
157 address from,
158 address to,
159 uint256 pizzaId,
160 bytes memory _data
161 ) public {
162 this.transferFrom(from, to, pizzaId);
163 require(_checkOnERC721Received(from, to, pizzaId, _data), "Must implement onERC721Received.");
164 }
165
166 /**
167 * Internal function to invoke `onERC721Received` on a target address
168 * The call is not executed if the target address is not a contract
169 */
170 function _checkOnERC721Received(
171 address from,
172 address to,
173 uint256 pizzaId,
174 bytes memory _data
175 ) internal returns (bool) {
176 if (!isContract(to)) {
177 return true;
178 }
179
180 bytes4 retval = IERC721Receiver(to).onERC721Received(
181 msg.sender,
182 from,
183 pizzaId,
184 _data
185 );
186 return (retval == _ERC721_RECEIVED);
187 }
188
189 // Burns a Pizza - destroys Token completely
190 // The `external` function modifier means this function is
191 // part of the contract interface and other contracts can call it
192 function burn(uint256 _pizzaId) external {
193 require(msg.sender != address(0), "Invalid address.");
194 require(_exists(_pizzaId), "Pizza does not exist.");
195 require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved.");
196
197 ownerPizzaCount[msg.sender] = SafeMath.sub(
198 ownerPizzaCount[msg.sender],
199 1
200 );
201 pizzaToOwner[_pizzaId] = address(0);
202 }
203
204 // Returns count of Pizzas by address
205 function balanceOf(address _owner) public view returns (uint256 _balance) {
206 return ownerPizzaCount[_owner];
207 }
208
209 // Returns owner of the Pizza found by id
210 function ownerOf(uint256 _pizzaId) public view returns (address _owner) {
211 address owner = pizzaToOwner[_pizzaId];
212 require(owner != address(0), "Invalid Pizza ID.");
213 return owner;
214 }
215
216 // Approves other address to transfer ownership of Pizza
217 function approve(address _to, uint256 _pizzaId) public {
218 require(msg.sender == pizzaToOwner[_pizzaId], "Must be the Pizza owner.");
219 pizzaApprovals[_pizzaId] = _to;
220 emit Approval(msg.sender, _to, _pizzaId);
221 }
222
223 // Returns approved address for specific Pizza
224 function getApproved(uint256 _pizzaId)
225 public
226 view
227 returns (address operator)
228 {
229 require(_exists(_pizzaId), "Pizza does not exist.");
230 return pizzaApprovals[_pizzaId];
231 }
232
233 /**
234 * Private function to clear current approval of a given token ID
235 * Reverts if the given address is not indeed the owner of the token
236 */
237 function _clearApproval(address owner, uint256 _pizzaId) private {
238 require(pizzaToOwner[_pizzaId] == owner, "Must be pizza owner.");
239 require(_exists(_pizzaId), "Pizza does not exist.");
240 if (pizzaApprovals[_pizzaId] != address(0)) {
241 pizzaApprovals[_pizzaId] = address(0);
242 }
243 }
244
245 /*
246 * Sets or unsets the approval of a given operator
247 * An operator is allowed to transfer all tokens of the sender on their behalf
248 */
249 function setApprovalForAll(address to, bool approved) public {
250 require(to != msg.sender, "Cannot approve own address");
251 operatorApprovals[msg.sender][to] = approved;
252 emit ApprovalForAll(msg.sender, to, approved);
253 }
254
255 // Tells whether an operator is approved by a given owner
256 function isApprovedForAll(address owner, address operator)
257 public
258 view
259 returns (bool)
260 {
261 return operatorApprovals[owner][operator];
262 }
263
264 // Takes ownership of Pizza - only for approved users
265 function takeOwnership(uint256 _pizzaId) public {
266 require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved.");
267 address owner = this.ownerOf(_pizzaId);
268 this.transferFrom(owner, msg.sender, _pizzaId);
269 }
270
271 // Checks if Pizza exists
272 function _exists(uint256 pizzaId) internal view returns (bool) {
273 address owner = pizzaToOwner[pizzaId];
274 return owner != address(0);
275 }
276
277 // Checks if address is owner or is approved to transfer Pizza
278 function _isApprovedOrOwner(address spender, uint256 pizzaId)
279 internal
280 view
281 returns (bool)
282 {
283 address owner = pizzaToOwner[pizzaId];
284 // Disable solium check because of
285 // https://github.com/duaraghav8/Solium/issues/175
286 // solium-disable-next-line operator-whitespace
287 return (spender == owner ||
288 this.getApproved(pizzaId) == spender ||
289 this.isApprovedForAll(owner, spender));
290 }
291
292 // Check if Pizza is unique and doesn't exist yet
293 modifier isUnique(string memory _name, uint256 _dna) {
294 bool result = true;
295 for (uint256 i = 0; i < pizzas.length; i++) {
296 if (
297 keccak256(abi.encodePacked(pizzas[i].name)) ==
298 keccak256(abi.encodePacked(_name)) &&
299 pizzas[i].dna == _dna
300 ) {
301 result = false;
302 }
303 }
304 require(result, "Pizza with such name already exists.");
305 _;
306 }
307
308 // Returns whether the target address is a contract
309 function isContract(address account) internal view returns (bool) {
310 uint256 size;
311 // Currently there is no better way to check if there is a contract in an address
312 // than to check the size of the code at that address.
313 // See https://ethereum.stackexchange.com/a/14016/36603
314 // for more details about how this works.
315 // TODO Check this again before the Serenity release, because all addresses will be
316 // contracts then.
317 // solium-disable-next-line security/no-inline-assembly
318 assembly {
319 size := extcodesize(account)
320 }
321 return size > 0;
322 }
323}
324
Exibir tudo
📋 Copiar

Leitura adicional

Confira a documentação do Solidity e do Vyper para uma visão geral mais completa dos contratos inteligentes:

Was this article helpful?