Aula 168: Funcionalidade de Permissão EIP-2612
A EIP-2612 introduz um novo recurso aos tokens ERC20 que permite aprovações sem a necessidade de taxa de gás. Esta especificação adiciona uma função permit
que permite que um detentor de tokens aprove um gastador sem precisar enviar uma transação e pagar as taxas de gás. Em vez disso, a aprovação pode ser assinada off-chain e enviada para o contrato inteligente, permitindo que o gastador utilize os tokens em nome do detentor.
Nesta aula, discutiremos como a função permit
funciona e forneceremos um exemplo de como implementá-la em um contrato inteligente em Solidity.
Entendendo a EIP-2612
A EIP-2612 inclui os seguintes recursos-chave:
- Função Permit: Permite que aprovações sejam feitas usando uma assinatura pessoal, que o contrato verifica.
- Nonces: Cada endereço recebe um nonce que deve ser utilizado para prevenir ataques de repetição.
- Expiração: A permissão pode ser configurada para expirar, garantindo que permissões assinadas não possam ser usadas indefinidamente.
A assinatura da função permit
é a seguinte:
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
Parâmetros da Função
owner
: O endereço do detentor dos tokens.spender
: O endereço autorizado a gastar os tokens.value
: O número de tokens a serem aprovados.deadline
: O timestamp após o qual a permissão não é mais válida.v
,r
,s
: Componentes da assinatura gerada off-chain.
Modificando o Contrato ERC20
Para implementar a funcionalidade de permissão em um token ERC20 padrão, siga estes passos:
- Adicionar Variáveis de Estado: Precisamos de um mapeamento para armazenar nonces e uma variável para o separador de domínio.
- Implementar a Função Permit: Utilize o padrão EIP-712 para hash da mensagem de permissão.
- Atualizar a função
approve
: Para integrar a gestão de nonces.
Aqui está uma implementação de exemplo:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract MyToken is ERC20, EIP712 {
mapping(address => uint256) public nonces;
// O nome da aplicação para EIP-712
string private constant SIGNING_DOMAIN = "MyToken";
string private constant SIGNATURE_VERSION = "1";
constructor() ERC20("MyToken", "MTK") EIP712(SIGNING_DOMAIN, SIGNATURE_VERSION) {}
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(block.timestamp <= deadline, "Permissão expirada");
// Calcular o nonce atual e incrementar
uint256 currentNonce = nonces[owner];
nonces[owner]++;
// Criar o digest para a permissão
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
owner,
spender,
value,
currentNonce,
deadline
)));
// Recuperar o endereço do signatário
address recoveredAddress = ECDSA.recover(digest, v, r, s);
require(recoveredAddress == owner, "Assinatura inválida");
// Aprovar o gastador
_approve(owner, spender, value);
}
}
Componentes Chave do Código
- Estruturas EIP712: A função
_hashTypedDataV4
gera o hash que corresponde à EIP-712. - Nonces: O mapeamento
nonces
é utilizado para acompanhar quantas permissões foram usadas, garantindo uso único. - Verificação de Assinatura: O uso do
ECDSA
nos permite recuperar o endereço do signatário e verificar se corresponde aoowner
.
Como Usar a Função Permit
Após implantar seu contrato de token:
- O usuário (detentor dos tokens) gera uma assinatura para a permissão com seu provedor web3 ou qualquer biblioteca off-chain, como ethers.js ou web3.js.
- O usuário envia os parâmetros da assinatura para um método do contrato inteligente que pode chamar a função
permit
.
Exemplo de geração de assinatura usando ethers.js:
const { ethers } = require("ethers");
async function generatePermit(owner, spender, value, nonce, deadline) {
const domain = {
name: "MyToken",
version: "1",
chainId: 1, // chainId da Mainnet
verifyingContract: "<SEU_ENDEREÇO_CONTRATO>"
};
const types = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" }
]
};
const message = {
owner: owner,
spender: spender,
value: value,
nonce: nonce,
deadline: deadline
};
const wallet = new ethers.Wallet("<SUA_CHAVE_PRIVADA>");
const signature = await wallet._signTypedData(domain, types, message);
return signature; // Separe isso em v, r, s para a chamada da transação
}
Conclusão
A funcionalidade de permissão da EIP-2612 melhora a usabilidade dos tokens ERC20 ao permitir aprovações sem custo de gás, o que pode melhorar significativamente a experiência do usuário. Seguindo os passos delineados nesta aula, você pode facilmente integrar a funcionalidade de permissão em seus contratos de token ERC20.