-
Notifications
You must be signed in to change notification settings - Fork 364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Initial implementation of auction dApp #2265
Open
mous1985
wants to merge
25
commits into
gnolang:master
Choose a base branch
from
mous1985:auction
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+533
−0
Open
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
6016549
mvp
mous1985 a5612ca
package realm
mous1985 d1f20c6
add emit event
mous1985 11f328c
wal
mous1985 efa50bf
add event
mous1985 96b652b
element in lowercase
mous1985 cc559a4
r
mous1985 1b2221c
pull request v0
mous1985 16ad976
fix error std.Emit()
mous1985 e143032
fix error std.Emit() n1
mous1985 581dbdf
Merge branch 'master' into auction
mous1985 9ca1253
fix error too many argument
mous1985 2b6c1f4
Merge branch 'auction' of https://github.com/mous1985/gno into auction
mous1985 a291aa1
Apply suggestions from code review
mous1985 e384536
Merge branch 'master' into auction
mous1985 6936b8b
update struct fields and delete the getter methods
mous1985 5e47aa9
delete newBid
mous1985 dd5b491
update EndAuction with other error cases
mous1985 0d94343
update auction_test.gno
mous1985 9d63472
update auction.gno realm
mous1985 9a8ec0a
update auction test realm
mous1985 af0ae2e
use std.Coin instead of price
mous1985 5b7ecaf
change IsOwnedBy with ownable
mous1985 45a2523
ownable
mous1985 9ebe6b9
update auciton_test.gno
mous1985 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package auction | ||
|
||
import ( | ||
"std" | ||
"strconv" | ||
"time" | ||
|
||
"gno.land/p/demo/ownable" | ||
"gno.land/p/demo/ufmt" | ||
) | ||
|
||
// Auction struct | ||
type Auction struct { | ||
Owner ownable.Ownable // Embeds Ownable to handle ownership | ||
Title string | ||
Description string | ||
Begin time.Time | ||
End time.Time | ||
StartingBid std.Coin | ||
Bids []*Bid | ||
State string // "upcoming", "ongoing", or "closed" | ||
} | ||
|
||
// Bid struct | ||
type Bid struct { | ||
Bidder std.Address | ||
Amount std.Coin | ||
} | ||
|
||
var ( | ||
auctionList []*Auction | ||
currentTime time.Time | ||
) | ||
|
||
// NewAuction creates a new auction | ||
func NewAuction( | ||
title string, | ||
owner std.Address, | ||
description string, | ||
begin time.Time, | ||
end time.Time, | ||
startingBid std.Coin, | ||
) *Auction { | ||
return &Auction{ | ||
Owner: *ownable.NewWithAddress(owner), // Initialize Ownable with the owner | ||
Title: title, | ||
Description: description, | ||
Begin: begin, | ||
End: end, | ||
StartingBid: startingBid, | ||
Bids: []*Bid{}, | ||
State: "upcoming", | ||
} | ||
} | ||
|
||
// AddBid adds a new bid to the auction | ||
func (a *Auction) AddBid(bidder std.Address, amount std.Coin) error { | ||
if time.Now().Before(a.Begin) { | ||
return ufmt.Errorf("auction: AddBid: auction has not started yet") | ||
} | ||
if time.Now().After(a.End) { | ||
return ufmt.Errorf("auction: AddBid: auction has already ended") | ||
} | ||
if !amount.IsGTE(a.StartingBid) { | ||
return ufmt.Errorf("auction: AddBid: bid amount must be higher than the current highest bid") | ||
} | ||
bid := &Bid{Bidder: bidder, Amount: amount} | ||
a.Bids = append(a.Bids, bid) | ||
a.StartingBid = amount | ||
std.Emit("BidPlaced", "auction", a.Title, "bidder", bidder.String(), "amount", amount.String()) | ||
return nil | ||
} | ||
|
||
// EndAuction ends the auction | ||
func (a *Auction) EndAuction() error { | ||
if err := a.Owner.CallerIsOwner(); err != nil { | ||
return err | ||
} | ||
if time.Now().Before(a.End) { | ||
return ufmt.Errorf("auction: EndAuction: auction cannot end before the end time") | ||
} | ||
if a.State == "closed" { | ||
return ufmt.Errorf("auction: EndAuction: auction is already closed") | ||
} | ||
if len(a.Bids) == 0 { | ||
std.Emit("AuctionEndedNoBids", "auction", a.Title) | ||
return ufmt.Errorf("auction: EndAuction: auction ended with no bids") | ||
} | ||
a.State = "closed" | ||
highestBid := a.StartingBid | ||
winner := a.Bids[len(a.Bids)-1].Bidder | ||
std.Emit("AuctionEnded", "winner", winner.String(), "amount", highestBid.String()) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
package auction | ||
|
||
import ( | ||
"std" | ||
"testing" | ||
"time" | ||
|
||
"gno.land/p/demo/ownable" | ||
"gno.land/p/demo/testutils" | ||
) | ||
|
||
func setCurrentTime(t time.Time) { | ||
currentTime = t | ||
} | ||
|
||
func resetCurrentTime() { | ||
currentTime = time.Time{} | ||
} | ||
|
||
func TestNewAuction(t *testing.T) { | ||
owner := std.Address("owner") | ||
begin := time.Now().Add(1 * time.Hour) | ||
end := time.Now().Add(24 * time.Hour) | ||
startingBid := std.NewCoin("ugnot", 100) | ||
|
||
auction := NewAuction("Test Auction", owner, "This is a test auction", begin, end, startingBid) | ||
|
||
if auction.Title != "Test Auction" { | ||
t.Fatalf("expected auction title to be 'Test Auction', got '%s'", auction.Title) | ||
} | ||
if auction.Owner.Owner() != owner { // Note the change here to access owner | ||
t.Fatalf("expected auction owner to be '%s', got '%s'", owner, auction.Owner.Owner()) | ||
} | ||
if auction.Description != "This is a test auction" { | ||
t.Fatalf("expected auction description to be 'This is a test auction', got '%s'", auction.Description) | ||
} | ||
if auction.Begin != begin { | ||
t.Fatalf("expected auction begin time to be '%s', got '%s'", begin, auction.Begin) | ||
} | ||
if auction.End != end { | ||
t.Fatalf("expected auction end time to be '%s', got '%s'", end, auction.End) | ||
} | ||
if !auction.StartingBid.IsEqual(startingBid) { | ||
t.Fatalf("expected auction starting bid to be '%s', got '%s'", startingBid.String(), auction.StartingBid.String()) | ||
} | ||
if auction.State != "upcoming" { | ||
t.Fatalf("expected auction state to be 'upcoming', got '%s'", auction.State) | ||
} | ||
} | ||
|
||
func TestAddBid(t *testing.T) { | ||
owner := std.Address("owner") | ||
bidder1 := std.Address("bidder1") | ||
bidder2 := std.Address("bidder2") | ||
begin := time.Now().Add(1 * time.Hour) | ||
end := time.Now().Add(24 * time.Hour) | ||
startingBid := std.NewCoin("ugnot", 100) | ||
|
||
auction := NewAuction("Test Auction", owner, "This is a test auction", begin, end, startingBid) | ||
|
||
// Test before auction starts | ||
setCurrentTime(time.Now()) | ||
err := auction.AddBid(bidder1, std.NewCoin("ugnot", 200)) | ||
if err == nil || err.Error() != "auction: AddBid: auction has not started yet" { | ||
t.Fatalf("expected error 'auction has not started yet', got '%v'", err) | ||
} | ||
resetCurrentTime() | ||
|
||
// Test successful bid | ||
setCurrentTime(begin.Add(1 * time.Second)) | ||
err = auction.AddBid(bidder1, std.NewCoin("ugnot", 200)) | ||
resetCurrentTime() | ||
|
||
if err != nil { | ||
t.Fatalf("expected no error, got '%v'", err) | ||
} | ||
if !auction.StartingBid.IsEqual(std.NewCoin("ugnot", 200)) { | ||
t.Fatalf("expected auction starting bid to be '200ugnot', got '%s'", auction.StartingBid.String()) | ||
} | ||
if len(auction.Bids) != 1 { | ||
t.Fatalf("expected number of bids to be '1', got '%d'", len(auction.Bids)) | ||
} | ||
if auction.Bids[0].Bidder != bidder1 { | ||
t.Fatalf("expected bidder to be 'bidder1', got '%s'", auction.Bids[0].Bidder) | ||
} | ||
|
||
// Test higher bid | ||
setCurrentTime(begin.Add(2 * time.Second)) | ||
err = auction.AddBid(bidder2, std.NewCoin("ugnot", 300)) | ||
resetCurrentTime() | ||
|
||
if err != nil { | ||
t.Fatalf("expected no error, got '%v'", err) | ||
} | ||
if !auction.StartingBid.IsEqual(std.NewCoin("ugnot", 300)) { | ||
t.Fatalf("expected auction starting bid to be '300ugnot', got '%s'", auction.StartingBid.String()) | ||
} | ||
if len(auction.Bids) != 2 { | ||
t.Fatalf("expected number of bids to be '2', got '%d'", len(auction.Bids)) | ||
} | ||
if auction.Bids[1].Bidder != bidder2 { | ||
t.Fatalf("expected bidder to be 'bidder2', got '%s'", auction.Bids[1].Bidder) | ||
} | ||
|
||
// Test bid lower than current price | ||
setCurrentTime(begin.Add(3 * time.Second)) | ||
err = auction.AddBid(bidder1, std.NewCoin("ugnot", 250)) | ||
resetCurrentTime() | ||
if err == nil || err.Error() != "auction: AddBid: bid amount must be higher than the current highest bid" { | ||
t.Fatalf("expected error 'bid amount must be higher than the current highest bid', got '%v'", err) | ||
} | ||
} | ||
|
||
func TestEndAuction(t *testing.T) { | ||
owner := testutils.TestAddress("owner") | ||
bidder := testutils.TestAddress("bidder") | ||
begin := time.Now().Add(1 * time.Hour) | ||
end := time.Now().Add(24 * time.Hour) | ||
startingBid := std.NewCoin("ugnot", 100) | ||
|
||
auction := NewAuction("Test Auction", owner, "This is a test auction", begin, end, startingBid) | ||
owner.AssertCallerIsOwner() | ||
// Test ending auction before it starts | ||
setCurrentTime(begin.Add(-1 * time.Hour)) | ||
|
||
err := auction.EndAuction() | ||
if err == nil || err.Error() != "auction: EndAuction: auction cannot end before the end time" { | ||
t.Fatalf("expected error 'auction cannot end before the end time', got '%v'", err) | ||
} | ||
resetCurrentTime() | ||
|
||
// Test ending auction with no bids | ||
setCurrentTime(end.Add(1 * time.Second)) | ||
err = auction.EndAuction() | ||
if err == nil || err.Error() != "auction: EndAuction: auction ended with no bids" { | ||
t.Fatalf("expected error 'auction ended with no bids', got '%v'", err) | ||
} | ||
resetCurrentTime() | ||
|
||
// Place a bid and end auction | ||
setCurrentTime(begin.Add(1 * time.Second)) | ||
auction.AddBid(bidder, std.NewCoin("ugnot", 200)) | ||
setCurrentTime(end.Add(1 * time.Second)) | ||
err = auction.EndAuction() | ||
resetCurrentTime() | ||
|
||
if err != nil { | ||
t.Fatalf("expected no error, got '%v'", err) | ||
} | ||
if auction.State != "closed" { | ||
t.Fatalf("expected auction state to be 'closed', got '%s'", auction.State) | ||
} | ||
if len(auction.Bids) == 0 { | ||
t.Fatalf("expected at least one bid to be present") | ||
} | ||
if auction.Bids[len(auction.Bids)-1].Bidder != bidder { | ||
t.Fatalf("expected winner to be 'bidder', got '%s'", auction.Bids[len(auction.Bids)-1].Bidder) | ||
} | ||
} | ||
|
||
// SetCurrentTime sets the current time for testing purposes | ||
func SetCurrentTime(t time.Time) { | ||
currentTime = t | ||
} | ||
|
||
// now returns the current time, allowing for mocking in tests | ||
func now() time.Time { | ||
if !currentTime.IsZero() { | ||
return currentTime | ||
} | ||
return time.Now() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module gno.land/p/demo/auction | ||
|
||
require ( | ||
gno.land/p/demo/ownable v0.0.0-latest | ||
gno.land/p/demo/ufmt v0.0.0-latest | ||
) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about having
begin
actually betime.Now
?This way you can decrease the param. It's only assuming you don't have auctions that start in the future, given that you emit an event when this constructor exits
end
can betime.Duration
if this is your preference.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the package with three auction states: “upcoming”, “ongoing”, and “closed”. That was my idea, so I could have auctions starting in the future.