Recentemente, tenho revisado meus conhecimentos de Solidity para consolidar os detalhes e estou escrevendo uma "Introdução Simples ao Solidity" para iniciantes (programadores avançados podem buscar outros tutoriais). Atualizo de 1 a 3 lições por semana.
Twitter: @0xAA_Science
Comunidade: Discord | Grupo do WeChat | Site oficial wtf.academy
Todo código e tutorial estão disponíveis no GitHub: github.com/AmazingAng/WTF-Solidity
Nesta lição, vamos usar o contrato de interface ERC721
para explicar contratos abstratos (abstract
) e interfaces (interface
) no Solidity, ajudando a entender melhor o padrão ERC721
.
Se um contrato Solidity possui pelo menos uma função não implementada, ou seja, uma função que falta conteúdo na chave {}
, então esse contrato deve ser marcado como abstract
, caso contrário haverá um erro de compilação. Além disso, as funções não implementadas devem ser marcadas como virtual
, para que possam ser sobrescritas pelos contratos filhos. Por exemplo, no contrato de ordenamento por inserção que vimos anteriormente, se ainda não decidimos como implementar a função de ordenação por inserção, podemos marcar o contrato como abstract
e deixar outros preencherem esse detalhe.
abstract contract InsertionSort {
function insertionSort(uint[] memory a) public pure virtual returns (uint[] memory);
}
As interfaces são semelhantes a contratos abstratos, mas não implementam funcionalidades. Regras para interfaces:
- Não podem conter variáveis de estado.
- Não podem conter construtores.
- Não podem herdar de contratos que não sejam interfaces.
- Todas as funções devem ser do tipo
external
e não devem ter um corpo de função. - Contratos que herdam de uma interface devem implementar todas as funções definidas na interface, exceto os eventos.
Embora as interfaces não implementem nenhuma funcionalidade, elas são muito importantes. As interfaces são o esqueleto de um contrato inteligente, definem as funcionalidades do contrato e como elas podem ser acionadas: se um contrato inteligente implementa uma determinada interface (como ERC20
ou ERC721
), outras DApps e contratos inteligentes sabem como interagir com ele. Isso porque as interfaces fornecem duas informações importantes:
-
O seletor
bytes4
para cada função do contrato e a assinatura da funçãonomeDaFuncao(tipoDeCadaParametro)
. -
O ID da interface (para mais informações, veja EIP165).
Além disso, as interfaces são equivalentes ao ABI (Application Binary Interface) de um contrato e podem ser convertidas uma na outra: compilando uma interface, podemos obter o ABI do contrato. Além disso, é possível converter um arquivo json
ABI em um arquivo sol
de interface usando a ferramenta abi-to-sol.
Vamos usar a interface do contrato IERC721
como exemplo, que define 3 eventos e 9 funções. Todos os NFTs que seguem o padrão ERC721
implementam essas funções. Podemos ver que a diferença entre uma interface e um contrato convencional é que cada função de uma interface termina com ;
enquanto em um contrato terminaria com {}
.
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function setApprovalForAll(address operator, bool _approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data) external;
}
O IERC721
possui 3 eventos, sendo que os eventos Transfer
e Approval
são encontrados também em contratos ERC20
.
- Evento
Transfer
: emitido durante transferências para registrar o endereço de origemfrom
, o endereço de destinoto
e otokenId
. - Evento
Approval
: emitido durante autorizações para registrar o endereço do proprietárioowner
, o endereço autorizadoapproved
e otokenId
. - Evento
ApprovalForAll
: emitido durante autorizações em massa para registrar o endereço de origemowner
, o endereço autorizadooperator
e se a autorização foi concedida (approved
).
balanceOf
: retorna o saldo de NFTs de um determinado endereçoowner
.ownerOf
: retorna o proprietário de um determinadotokenId
.transferFrom
: transfere um NFT normalmente, com os parâmetros sendo o endereço de origemfrom
, o endereço de destinoto
e otokenId
.safeTransferFrom
: transfere um NFT com segurança (se o destino for um contrato, o contrato precisa implementar a interfaceERC721Receiver
). Os parâmetros são o endereço de origemfrom
, o endereço de destinoto
e otokenId
.approve
: autoriza outro endereço a utilizar seu NFT. Os parâmetros são o endereço autorizadoapprove
e otokenId
.getApproved
: consulta qual endereço foi autorizado a utilizar um determinadotokenId
.setApprovalForAll
: concede autorização em massa dos seus NFTs para um determinado endereçooperator
.isApprovedForAll
: verifica se um determinado endereço autorizou em massa outro endereçooperator
.safeTransferFrom
: uma sobrecarga da função de transferência segura, com o parâmetrodata
adicionado.
Se soubermos que um contrato implementa a interface IERC721
, não precisamos conhecer a implementação do código, apenas sabemos a função e como interagir com ele.
Por exemplo, o token Bored Ape Yacht Club (BAYC)
é um NFT do tipo ERC721
, que implementa as funções definidas na interface IERC721
. Não precisamos saber o código fonte, apenas o endereço do contrato. Com a interface IERC721
, podemos interagir com o contrato, como consultar o saldo de um endereço com balanceOf()
ou transferir um NFT com safeTransferFrom()
.
contract interactBAYC {
// Criar uma variável de interface para o contrato BAYC (na mainnet do Ethereum)
IERC721 BAYC = IERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D);
// Consultar o saldo de BAYC de um endereço usando a função balanceOf()
function balanceOfBAYC(address owner) external view returns (uint256 balance) {
return BAYC.balanceOf(owner);
}
// Transferir BAYC de forma segura usando a função safeTransferFrom()
function safeTransferFromBAYC(address from, address to, uint256 tokenId) external {
BAYC.safeTransferFrom(from, to, tokenId);
}
}
-
Exemplo de contrato abstrato (um código de demonstração simples está na imagem abaixo)
-
Exemplo de interface (um código de demonstração simples está na imagem abaixo)
Nesta lição, expliquei os contratos abstratos (abstract
) e interfaces (interface
) no Solidity, que são úteis para escrever modelos e reduzir a repetição de código. Também discutimos a interface do contrato IERC721
e como interagir com o contrato do Bored Ape Yacht Club (BAYC
) usando essa interface.