W poprzednim tutorialu dowiedziałeś się, jak stworzyć smart contract, który jest walletem. W tym tutorialu postaramy się utrwalić wiedzę z poprzedniego poradnika oraz poszerzyć ją o nową.
Przejdźmy więc do dalej. Wszystkie rzeczy, których możesz nie rozumieć wyjaśnimy Ci w dalszej części tekstu.
Zdefiniowanie zmiennych
Zacznijmy od zdefiniowania zmiennych, które będą nam potrzebne do funkcjonowania smart kontraktu. Na pewno potrzebujemy :
- adres osoby do której zostanie przesłana najwyższa oferta,
- czas, kiedy dana aukcja się zakończy,
- adres osoby, która wystawiła najwyższą ofertę,
- ile wei to najwyższa oferta,
- tablicę adresów i ilość pieniędzy jaką przelali do smart kontraktu, aby osoby, które nie wygrały aukcji, mogły wypłacić swoje pieniądze,
- zmienną czy aukcja jest zakończona lub nie.
pragma solidity 0.8.11;
contract Auction {
address payable public beneficiary;
uint public auctionEndTime;
address public highestBidder;
uint public highestBid;
mapping(address => uint) pendingReturns;
bool ended;
}
Czego możesz nie zrozumieć z powyższego kodu?
Mapping w solidity to tablica klucz-wartość, odpowiednik dictionary w innych językach.
Jeśli nie wiesz czemu zmienna auctionEndTime
jest podana w zwykłym uintcie, to jest to z tego powodu, że czas kiedy aukcja się zakończy będzie podany w czasie unixowym.
Stwórzmy sobie constructor, który będzie przyjmował adres do którego zostanie przesłana najwyższa oferta oraz ile czasu będzie trwała aukcja.
constructor(
uint biddingTime,
address payable beneficiaryAddress
) {
beneficiary = beneficiaryAddress;
auctionEndTime = block.timestamp + biddingTime;
}
Na co warto zwrócić uwagę ?
block.timestamp
to zmienna, która oznacza po prostu jaki jest teraz czas podany oczywiście jako czas unixowy.
Funkcja do składania ofert
Teraz stwórzmy sobie funkcję, która będzie służyła do składania swojej oferty. Funkcja ma zwracać błąd jeśli :
- aukcja się zakończyła,
- nasza oferta będzie niższa niż najwyższa oferta.
Na zakończenie wykonywania funkcji, funkcja wyemituje event, mówiący o tym że najwyższa oferta się zmienia. Aplikacja frontendowa może nasłuchiwać emitowanych eventów na smart contracie, dzięki czemu, gdy tylko najwyższa oferta się zmieni, może ona ją zaktualizować w interfejsie użytkownika.
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
error AuctionAlreadyEnded();
error BidNotHighEnough(uint highestBid);
error AuctionNotYetEnded();
error AuctionEndAlreadyCalled();
function bid() external payable {
if (block.timestamp > auctionEndTime)
revert AuctionAlreadyEnded();
if (msg.value <= highestBid)
revert BidNotHighEnough(highestBid);
if (highestBid != 0) {
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
Jak widzisz w powyższym kodzie definiowane są eventy z parametrami, które możemy wyemitować na blockchainie. Aby wyemitować event w solidity wpisujemy
emit + nazwa eventu i parametry
Zdefiniowaliśmy sobie swoje własne errory, które jeśli chcemy wywołać wpisujemy
revert + nazwa naszego erroru i parametry.
Zapewne domyśliłeś się już, że zamiast ifów i custom errorów mogliśmy użyć requiera.
Ta funkcja sprawdza czy aukcja się już nie zakończyła, czy msg.value jest wyższy niż najwyższa oferta, jeśli tak aktualizujemy mapping pendingReturns
, żeby osoba, która złożyła wcześniej najwyższą ofertę mogła dostać z powrotem swoje pieniądze. Przypisujemy najwyższą ofertę do msg.value
oraz highestBidder
do msg.sender
, na końcu wykonywania funkcji emitujemy event, który informuje, że najwyższa oferta została zwiększona.
Funkcja do zakończenia aukcji, oraz przelania najwyższej oferty do beneficiera
Teraz stwórzmy funkcję dzięki, której po zakończonej aukcji adres beneficiary, będzie mógł przesłać do swojego portfela pieniądze.
Ta funkcja powinna:
- zwracać błąd jeśli aukcja się jeszcze nie skończyła,
- zwracać błąd jeśli ta funkcja została już wywołana,
- zmieniać zmienna ended na true,
- wyemitować event świadczący o tym, że aukcja się zakończyła,
- przesłać ilość Ethereum równoważną do najwyższej oferty do beneficiera.
function auctionEnd() external {
if (block.timestamp < auctionEndTime)
revert AuctionNotYetEnded();
if (ended)
revert AuctionEndAlreadyCalled();
ended = true;
emit AuctionEnded(highestBidder, highestBid);
beneficiary.transfer(highestBid);
}
Funkcja dla osób, które nie wygrały aukcji i chcą odzyskać swoje pieniądze
Teraz, ostatnia funkcja, której potrzebujemy, aby nasz kontrakt był gotowy! Będzie ona dla adresów, które nie wygrały aukcji, a chcą odzyskać swoje pieniądze. Zastanówmy się co powinna mieć w sobie taka funkcja :
- powinna sprawdzać ile należy ci się ethereum w mappingu pendingReturns oraz przypisać tą wartość do zmiennej,
- powinna zmienić ile ci się należy na 0,
- powinna przesłać tyle Ethereum ile zapisaliśmy w zmiennej.
No to do dzieła !
function withdraw() external{
uint amount = pendingReturns[msg.sender];
pendingReturns[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
To już koniec dzisiejszego tutorialu ! Nasz smart contract jest gotowy. W celu poćwiczenia oraz utrwalenia sobie wiedzy, jako zadanie możesz spróbować zamienić custom errory na requiry. Jeśli to jednak dla ciebie za mało możesz również ulepszyć ten kontrakt, aby dało się w nim prowadzić kilka aukcji na raz.