2 de maio de 2023
# Foto de Zoltan Tasi no Unsplash
Antes de entrarmos nos detalhes da criação de um contrato inteligente de leilão holandês usando o Foundry, é importante observar que este artigo considera que você tenha uma compreensão básica de desenvolvimento de contratos inteligentes e os motivos por trás do uso de uma plataforma blockchain como o Foundry. Comparado a outras ferramentas populares como o HardHat, o Foundry oferece melhor desempenho e permite testes no Solidity, tornando-se uma excelente escolha para desenvolvedores que priorizam eficiência e segurança em seus contratos inteligentes.
Com isso em mente, agora podemos explorar o processo passo a passo de construção de um contrato inteligente básico de leilão holandês com o Foundry.
Vamos direto ao assunto.
Um leilão holandês é um tipo de leilão em que o preço do item vendido começa alto e depois diminui até que um licitante esteja disposto a pagar o preço atual.
- Em um leilão holandês básico, o vendedor define o preço inicial e um preço mínimo que está disposto a aceitar.
- O leilão começa com o preço mais alto e depois diminui gradualmente a uma taxa predeterminada até que um licitante faça um lance ou o preço mínimo seja atingido.
- O primeiro licitante a fazer um lance no preço atual vence o leilão e paga esse preço pelo item.
Primeiro passo, vamos instalar o Foundry. Existem várias maneiras de fazer isso, vou fazer isso usando o foundryup. Outras formas de instalação estão listadas no livro do Foundry.
Coloque os seguintes comandos no terminal:
> curl -L https://foundry.paradigm.xyz | bash
> foundryup
Você deve ver algo assim depois de executar o comando foundryup.
# Figura 1: Foundry instalado.
Vemos que a versão mais recente do Forge está instalada. Nós o usaremos para testar nossos contratos inteligentes mais tarde.
O próximo passo é criar um projeto. Em seu terminal vá para o diretório necessário e crie um novo projeto DutchAuction usando o seguinte comando.
> forge init DutchAuction
Assim que este comando for executado com sucesso, você poderá ver uma estrutura de pastas como esta.
|
|__lib
|__script
|__src
|__test
A pasta src conterá todos os seus contratos inteligentes, a pasta script terá os scripts de implantação e a pasta test abrigará os testes para os contratos inteligentes.
Escrevendo o contrato inteligente
Em sua pasta /src, crie um novo arquivo e nomeie-o como BasicDutchAuction.sol. Adicione o seguinte código a ele:
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.19;
contract BasicDutchAuction {
address payable public owner;
address payable public winner;
uint256 public auctionEndBlock;
uint256 public reservePrice;
uint256 public numBlocksActionOpen;
uint256 public offerPriceDecrement;
uint startBlockNumber;
uint public winningBidAmount;
bool public auctionEnded;
uint public initialPrice;
uint public currentPrice;
constructor(uint256 _reservePrice, uint256 _numBlocksAuctionOpen, uint256 _offerPriceDecrement) {
reservePrice = _reservePrice;
numBlocksActionOpen = _numBlocksAuctionOpen;
offerPriceDecrement = _offerPriceDecrement;
owner = payable(msg.sender);
startBlockNumber = block.number;
auctionEndBlock = block.number + numBlocksActionOpen;
initialPrice = _reservePrice + (_offerPriceDecrement * _numBlocksAuctionOpen);
currentPrice = initialPrice;
auctionEnded = false;
}
function updatePrice() internal {
if (block.number >= auctionEndBlock) {
auctionEnded = true;
return;
}
currentPrice = initialPrice - (offerPriceDecrement * (block.number - startBlockNumber));
}
function bid() public payable returns(address) {
require(msg.sender != owner, "Owner cannot bid");
if(auctionEnded && winner != address(0) && msg.sender != winner) {
address payable refundCaller = payable(msg.sender);
refundCaller.transfer(address(this).balance);
}
// verifica se o leilão foi encerrado
require(!auctionEnded, "Auction has ended");
// verifica se o número do bloco está dentro do limite de tempo
require(block.number < auctionEndBlock, "Auction has ended");
updatePrice();
// // verifica se o lance é maior do que o preço de reserva
require(msg.value >= currentPrice, "Bid is lower than current price");
require(winner == address(0), "Auction has already been won");
// se o valor do lance for maior, encerrar o leilão e transferir os fundos para o proprietário
auctionEnded = true;
winner = payable(msg.sender);
owner.transfer(msg.value);
winningBidAmount = msg.value;
return winner;
}
}
O que está acontecendo aqui?
Criamos um contrato inteligente chamado BasicDutchAuction que possui um construtor que usa três parâmetros — _reservePrice, _numBlocksAuctionOpen e _offerPriceDecrement.
- _reservePrice — é o preço mínimo que o leilão aceitará para o produto. O “produto” é uma entidade física aqui que assumimos que será transferida para o vencedor após o término do leilão.
- _numBlocksAuctionOpen — é o número de blocos ou lotes de transações para os quais o leilão será aberto .
- _offerPriceDecrement — é o valor que diminuirá do preço atual após o término de cada bloco.
O construtor também calcula o valor initialPrice e atualiza todos os outros campos no contrato inteligente.
updatePrice é uma função utilitária interna usada para calcular o preço mais recente do item.
bid() é a função de lance que um licitante chamará para participar do leilão holandês. Temos muitas outras verificações antes de declararmos um vencedor, os não vencedores são imediatamente reembolsados de seus valores de lance.
Agora que temos o código do contrato, vamos verificar se tudo está funcionando corretamente. Execute o seguinte comando para compilar e criar o contrato inteligente.
> forge build
# Figura 2: Os contratos são construídos e compilados com sucesso.
Testando o contrato de leilão holandês:
Agora que escrevemos o código para o contrato de leilão holandês, é hora de testá-lo. Usaremos o Forge para testar nosso contrato inteligente.
O Forge pode executar seus testes com o comando forge test. Todos os testes são escritos em Solidity.
O Forge vai procurar os testes em qualquer lugar do seu diretório de origem. Qualquer contrato com uma função que comece com test é considerado um teste. Normalmente, os testes serão colocados em test/ por convenção e terminarão com .t.sol.
Vamos criar um arquivo BasicDutchAuction.t.sol no diretório de testes. Adicione o seguinte código no arquivo. Observe que o teste do contrato inteligente do leilão holandes é feito por outro contrato inteligente, no nosso caso, o contrato BasicDutchAuctionTest, isso garante que os testes também sejam escritos em Solidity.
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/BasicDutchAuction.sol";
contract BasicDutchAuctionTest is Test {
BasicDutchAuction auction;
function setUp() public {
auction = new BasicDutchAuction(1000, 100, 10);
}
function testInitialPrice() public {
assertEq(auction.initialPrice(), 2000, "Initial price should be 2000");
}
function testCurrentPrice() public {
assertEq(auction.currentPrice(), 2000, "Current price should be 2000");
}
function testBid() public {
// Garante que um proprietário (owner) não possa fazer lances
// endereço do proprietário (owner) = auction.owner();
uint256 initialPrice = auction.initialPrice();
uint256 currentPrice = auction.currentPrice();
uint256 balanceBefore = address(this).balance;
uint256 bidAmount = currentPrice + 1;
try auction.bid{value: bidAmount}() {
vm.expectRevert(bytes("Owner cannot bid"));
} catch Error(string memory reason) {
console.log('reason: ', reason);
}
address payable someRandomUser = payable(vm.addr(1));
vm.deal(someRandomUser, 1 ether);
vm.startPrank(someRandomUser);
assertEq(address(this).balance, balanceBefore, "Contract balance should not change after owner tries to bid");
// Garante que um lance inferior ao preço de reserva seja rejeitado
balanceBefore = address(this).balance;
bidAmount = initialPrice - 1;
assertEq(address(this).balance, balanceBefore, "Contract balance should not change after bid lower than reserve price is rejected");
// Garante que um lance superior ao preço de reserva seja aceito
balanceBefore = address(this).balance;
bidAmount = initialPrice + 1;
address winningBidder = auction.bid{value: bidAmount}();
assertEq(winningBidder, someRandomUser, "The winning bidder should be returned by the bid function");
assertEq(auction.winner(), someRandomUser, "The winner should be the bidder who made the highest bid");
assertEq(auction.winningBidAmount(), bidAmount, "The winning bid amount should be the highest bid made");
uint256 blocksToWait = 5;
uint256 blocksElapsed = 0;
while (blocksElapsed < blocksToWait) {
blocksElapsed++;
}
vm.stopPrank();
}
}
O contrato BasicDutchAuctionTest estende o contrato de teste da biblioteca padrão do Forge que implementa várias funções que usaremos para nossos propósitos de teste.
A função setUp() é executada antes de cada caso de teste, semelhante à função beforeEach() no framework de testes Mocha.
Na maioria das vezes, simplesmente testar suas saídas de contratos inteligentes não é suficiente. Para manipular o estado da blockchain, bem como testar reversões e eventos específicos, o Foundry é fornecido com um conjunto de cheatcodes (códigos de trapaça). Os cheatcodes permitem que você altere o número do bloco, sua identidade e muito mais.
Você pode acessar os cheatcodes facilmente através da instância vm
disponível no contrato Test
da biblioteca padrão do Forge.
Usamos o cheatcode “prank” em nosso teste para simular os lances vindos de uma conta não proprietária. Também usamos o cheatcode “deal” para adicionar alguns Ethers à conta de testes.
Agora podemos executar os testes usando o seguinte comando.
> forge test
# Figura 3: Testando o contrato inteligente
Para modificar o nível de detalhamento fornecido pelos testes, podemos utilizar o sinalizador -v. Dependendo de suas preferências, você pode incluir até cinco v para aumentar o detalhamento da saída.
> forge test -vvvv
O comando acima fornece a seguinte saída:
# Figura 4: Adiciona mais complexidade aos seus testes.
Podemos gerar o relatório de cobertura usando o seguinte comando:
> forge coverage
Boas notícias! Concluímos a tarefa de criar e verificar a funcionalidade do nosso contrato inteligente de leilão holandês no Foundry. Se você deseja desenvolver ainda mais este projeto, considere adicionar casos de teste adicionais para cobrir todos os aspectos do contrato inteligente, implementando a capacidade de leiloar NFTs e permitindo a participação com um token personalizado diferente do ETH.
Para o código usado neste artigo, verifique o repositório Git associado.
Além disso, você pode explorar a Dutch Auction Application (Aplicação de leilão holandês) e descobrir vários aprimoramentos que podem ser implementados neste repositório do Hardhat.
Esse artigo foi escrito por Arjun Raja e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.
Top comments (0)