Anatomia dos contratos inteligentes
Última edição: @matheusrrugolo(opens in a new tab), 14 de junho de 2024
Um contrato inteligente (smart contract) é um programa executado em um endereço na Ethereum. Eles são compostos por dados e funções que podem ser executadas ao receber uma transação. Veja aqui uma visão geral do que compõe um contrato inteligente.
Pré-requisitos
Não deixe de 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. É necessário declarar o tipo para que o contrato possa manter um registro de quanto espaço na blockchain será necessário quando ele compilar.
1// Exemplo Solidity2contract SimpleStorage {3 uint storedData; // State variable4 // ...5}Copiar
1# Exemplo Vyper2storedData: int128Copiar
Se você já programou linguagens orientadas a objetos, provavelmente você estará familiarizado com a maioria dos tipos. Entretanto, 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
- Literais de strings
- Literais hexadecimais
- 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 estes não são armazenados permanentemente na blockchain, são muito mais baratos de usar.
Saiba mais sobre como a EVM armazena dados (Storage, Memória e Stack) em Solidity docs(opens in a new tab).
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 blockchain (cadeia de blocos) ou transação atual.
Exemplos:
Prop | Variável de estado | Descrição |
---|---|---|
block.timestamp | uint256 | Data/hora de início do bloco atual |
msg.sender | endereço | Remetente 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, masthis.f()
funciona).
- 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
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 mensagensfunçõ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 Solidity2function update_name(string value) public {3 dapp_name = value;4}Copiar
- O parâmetro
valor
do tipostring
é 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// Exemplo2function balanceOf(address _owner) public view return (uint256 _balance) {3 return ownerPizzaCount[_owner];4}Copiar
1dappName: public(string)23@view4@public5def readName() -> string:6 return dappNameCopiar
O que é considerado como modificar estado:
- Escrevendo variáveis de estado.
- Emitir eventos(opens in a new tab).
- Criação de outros contratos(opens in a new tab).
- Usando
autodestruct
. - Enviando ether por chamadas.
- Chamar qualquer função não marcada como
view
oupuro
. - Usando chamadas de baixo nível.
- 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 Solidity2// 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-properties9 owner = msg.sender;10}Exibir tudoCopiar
1# Exemplo Vyper23@external4def __init__(_beneficiary: endereço, _bidding_time: uint256):5 mesmo. eneficiário = _beneficiário6 self.auctionStart = block.timestamp7 self.auctionEnd = self.auctionStart + _bidding_timeCopiar
Funções integradas
Além das variáveis definidas no seu contrato, existem algumas variáveis globais especiais. O exemplo mais óbvio é:
address.send()
– SolidityEnviar(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;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}Exibir tudoCopiar
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 blockchain 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(opens in a new tab).
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#pragma3pragma solidity ^0.5.10;45// 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.html9contract HelloWorld {1011 // 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 contrato14 // e cria uma função que outros contratos ou clientes podem chamar para acessar o valor.15 mensagem pública de cadeia;1617 // 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#constructors21 constructor(string memory initMessage) public {22 // Aceita um argumento de string `initMessage` e define o valor23 // na variável de armazenamento `message` do contrato).24 message = initMessage;25 }2627 // Uma função pública que aceita um argumento de string28 // e atualiza a variável de armazenamento `message`.29 function update(string memory newMessage) public {30 message = newMessage;31 }32}Exibir tudoCopiar
Token
1pragma solidity ^0.5.10;23contract 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#address7 address public owner;89 // 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-types12 mapping (address => uint) public balances;1314 // Eventos permitem registro de atividade no blockchain.15 // Clientes Ethereum podem ouvir eventos para reagir às alterações do estado do contrato.16 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#events17 event Transfer(address from, address to, uint amount);1819 // Initializes the contract's data, setting the `owner`20 // to the address of the contract creator.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-properties26 owner = msg.sender;27 }2829 // 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-exceptions3536 // Somente o proprietário do contrato pode chamar esta função37 require(msg. remetente == dono, "Você não é o dono. );3839 // Reforça uma quantidade máxima de tokens40 require(amount < 1e60, "Emissão máxima excedida");4142 // Aumenta o saldo de `receiver` em `amount`43 saldos[receiver] += amount;44 }4546 // 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 enviar49 require(amount <= balances[msg.sender], "Insufficient balance.");5051 // Ajusta os saldos do token dos dois endereços52 balances[msg.sender] -= quantidade;53 balances[receiver] += quantidade;5455 // Emite um evendo definido anteriormente56 emite Transfer(msg.sender, receiver, amount);57 }58}Exibir tudoCopiar
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-files45import "../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";910// 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#inheritance13contract 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#SafeMath16 usando SafeMath para uint256;1718 // Variáveis de estado constantes em Solidity são semelhantes a outros idiomas19 // 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-variables21 uint256 constant dnaDigits = 10;22 uint256 constant dnaModulus = 10 ** dnaDigits;23 bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;2425 // Struct types let you define your own type26 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#structs27 struct Pizza {28 string name;29 uint256 dna;30 }3132 // Creates an empty array of Pizza structs33 Pizza[] public pizzas;3435 // Mapping from pizza ID to its owner's address36 mapping(uint256 => address) public pizzaToOwner;3738 // Mapping from owner's address to number of owned token39 mapping(address => uint256) public ownerPizzaCount;4041 // Mapping from token ID to approved address42 mapping(uint256 => address) pizzaApprovals;4344 // You can nest mappings, this example maps owner to operator approvals45 mapping(address => mapping(address => bool)) private operatorApprovals;4647 // Internal function to create a random Pizza from string (name) and DNA48 function _createPizza(string memory _name, uint256 _dna)49 // The `internal` keyword means this function is only visible50 // within this contract and contracts that derive this contract51 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#visibility-and-getters52 internal53 // `isUnique` is a function modifier that checks if the pizza already exists54 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html#function-modifiers55 isUnique(_name, _dna)56 {57 // Adds Pizza to array of Pizzas and get id58 uint256 id = SafeMath.sub(pizzas.push(Pizza(_name, _dna)), 1);5960 // Checks that Pizza owner is the same as current user61 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions6263 // note that address(0) is the zero address,64 // indicating that pizza[id] is not yet allocated to a particular user.6566 assert(pizzaToOwner[id] == address(0));6768 // Maps the Pizza to the owner69 pizzaToOwner[id] = msg.sender;70 ownerPizzaCount[msg.sender] = SafeMath.add(71 ownerPizzaCount[msg.sender],72 173 );74 }7576 // 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 }8182 // Generates random DNA from string (name) and address of the owner (creator)83 function generateRandomDna(string memory _str, address _owner)84 public85 // Functions marked as `pure` promise not to read from or modify the state86 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#pure-functions87 pure88 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 }9697 // Returns array of Pizzas found by owner98 function getPizzasByOwner(address _owner)99 public100 // Functions marked as `view` promise not to modify state101 // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#view-functions102 view103 returns (uint256[] memory)104 {105 // Uses the `memory` storage location to store values only for the106 // 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-stack108 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 }118119 // Transfers Pizza and ownership to other address120 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.");125126 ownerPizzaCount[_to] = SafeMath.add(ownerPizzaCount[_to], 1);127 ownerPizzaCount[_from] = SafeMath.sub(ownerPizzaCount[_from], 1);128 pizzaToOwner[_pizzaId] = _to;129130 // Emits event defined in the imported IERC721 contract131 emit Transfer(_from, _to, _pizzaId);132 _clearApproval(_to, _pizzaId);133 }134135 /**136 * Safely transfers the ownership of a given token ID to another address137 * If the target address is a contract, it must implement `onERC721Received`,138 * which is called upon a safe transfer, and return the magic value139 * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`;140 * otherwise, the transfer is reverted.141 */142 function safeTransferFrom(address from, address to, uint256 pizzaId)143 public144 {145 // solium-disable-next-line arg-overflow146 this.safeTransferFrom(from, to, pizzaId, "");147 }148149 /**150 * Safely transfers the ownership of a given token ID to another address151 * If the target address is a contract, it must implement `onERC721Received`,152 * which is called upon a safe transfer, and return the magic value153 * `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 _data161 ) public {162 this.transferFrom(from, to, pizzaId);163 require(_checkOnERC721Received(from, to, pizzaId, _data), "Must implement onERC721Received.");164 }165166 /**167 * Internal function to invoke `onERC721Received` on a target address168 * The call is not executed if the target address is not a contract169 */170 function _checkOnERC721Received(171 address from,172 address to,173 uint256 pizzaId,174 bytes memory _data175 ) internal returns (bool) {176 if (!isContract(to)) {177 return true;178 }179180 bytes4 retval = IERC721Receiver(to).onERC721Received(181 msg.sender,182 from,183 pizzaId,184 _data185 );186 return (retval == _ERC721_RECEIVED);187 }188189 // Burns a Pizza - destroys Token completely190 // The `external` function modifier means this function is191 // part of the contract interface and other contracts can call it192 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.");196197 ownerPizzaCount[msg.sender] = SafeMath.sub(198 ownerPizzaCount[msg.sender],199 1200 );201 pizzaToOwner[_pizzaId] = address(0);202 }203204 // Returns count of Pizzas by address205 function balanceOf(address _owner) public view returns (uint256 _balance) {206 return ownerPizzaCount[_owner];207 }208209 // Returns owner of the Pizza found by id210 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 }215216 // Approves other address to transfer ownership of Pizza217 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 }222223 // Returns approved address for specific Pizza224 function getApproved(uint256 _pizzaId)225 public226 view227 returns (address operator)228 {229 require(_exists(_pizzaId), "Pizza does not exist.");230 return pizzaApprovals[_pizzaId];231 }232233 /**234 * Private function to clear current approval of a given token ID235 * Reverts if the given address is not indeed the owner of the token236 */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 }244245 /*246 * Sets or unsets the approval of a given operator247 * An operator is allowed to transfer all tokens of the sender on their behalf248 */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 }254255 // Tells whether an operator is approved by a given owner256 function isApprovedForAll(address owner, address operator)257 public258 view259 returns (bool)260 {261 return operatorApprovals[owner][operator];262 }263264 // Takes ownership of Pizza - only for approved users265 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 }270271 // Checks if Pizza exists272 function _exists(uint256 pizzaId) internal view returns (bool) {273 address owner = pizzaToOwner[pizzaId];274 return owner != address(0);275 }276277 // Checks if address is owner or is approved to transfer Pizza278 function _isApprovedOrOwner(address spender, uint256 pizzaId)279 internal280 view281 returns (bool)282 {283 address owner = pizzaToOwner[pizzaId];284 // Disable solium check because of285 // https://github.com/duaraghav8/Solium/issues/175286 // solium-disable-next-line operator-whitespace287 return (spender == owner ||288 this.getApproved(pizzaId) == spender ||289 this.isApprovedForAll(owner, spender));290 }291292 // Check if Pizza is unique and doesn't exist yet293 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 == _dna300 ) {301 result = false;302 }303 }304 require(result, "Pizza with such name already exists.");305 _;306 }307308 // Returns whether the target address is a contract309 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 address312 // than to check the size of the code at that address.313 // See https://ethereum.stackexchange.com/a/14016/36603314 // for more details about how this works.315 // TODO Check this again before the Serenity release, because all addresses will be316 // contracts then.317 // solium-disable-next-line security/no-inline-assembly318 assembly {319 size := extcodesize(account)320 }321 return size > 0;322 }323}Exibir tudoCopiar
Leitura adicional
Confira a documentação do Solidity e do Vyper para uma visão geral mais completa dos contratos inteligentes:
Tópicos relacionados
Tutoriais relacionados
- Diminuir contratos para enfrentar o limite de tamanho do contrato – Algumas dicas práticas para reduzir o tamanho de seu contrato inteligente.
- Registrando dados de contratos inteligentes com eventos – Uma introdução aos eventos de contratos inteligentes e como você pode usá-los para registrar dados.
- Interaja com outros contratos da Solidity – Como implantar um contrato inteligente a partir de um contrato existente e interagir com ele.