Skip to content

Commit

Permalink
feat:refund
Browse files Browse the repository at this point in the history
  • Loading branch information
mous1985 committed Sep 18, 2024
1 parent b54b96c commit 0ef8ee9
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 67 deletions.
112 changes: 80 additions & 32 deletions api/r/auction/auction.gno
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ import (
"strconv"
"time"

auctionp "gno.land/p/demo/auction"
"gno.land/p/demo/mux"
)

// List to store all auctions
var (
auctionList []*auctionp.Auction
auctionList []*Auction
currentTime time.Time
// router *mux.Router
router = mux.NewRouter()
)

var router = mux.NewRouter()
// Map to store deposits for all bidders in the realm
var globalDeposits = make(map[std.Address]std.Coin)

// Initializes the router and handles routes for the auction realm
func init() {
router.HandleFunc("", renderHomepage)
router.HandleFunc("create/", renderCreateAuction)
Expand All @@ -26,58 +28,102 @@ func init() {
router.HandleFunc("/auction/{id}", renderAuctionDetails)
}

// CreateAuction handles the creation of a new auction
func CreateAuction(title, description string, begin, end int64, price uint64) string {
// CreateAuction handles creating a new auction and appends it to the auctionList
func CreateAuction(title, description string, begin, end int64, price std.Coin) string {
owner := std.GetOrigCaller()
beginTime := time.Unix(begin, 0)
endTime := time.Unix(end, 0)
auction := auctionp.NewAuction(title, owner, description, beginTime, endTime, price)
auctionList = append(auctionList, auction)
auction := NewAuction(title, owner, description, beginTime, endTime, price)
auctionList = append(auctionList, auction) // Add the auction to the list
return "Auction created successfully"
}

// PlaceBid handles placing a bid on an auction
func PlaceBid(id int, amount uint64) string {
// AddBid handles placing a bid on an auction identified by its ID
func AddBid(id int, amount std.Coin) string {
caller := std.GetOrigCaller()

// Validate auction ID
if id >= len(auctionList) || id < 0 {
panic("Invalid auction ID")
}
auction := auctionList[id]

// Get Banker instance for handling transactions
banker := std.GetBanker(std.BankerTypeOrigSend)

// Check if the bidder already has a deposit in the global realm
existingDeposit, exists := globalDeposits[caller]
if exists {
// Calculate additional deposit required
additionalAmount := amount.Sub(existingDeposit)
if additionalAmount.IsPositive() {
// Transfer the additional amount from the bidder to the auction realm
banker.SendCoins(caller, std.CurrentRealm().Addr(), std.NewCoins(additionalAmount))
}
} else {
// New bidder: deposit the full amount
banker.SendCoins(caller, std.CurrentRealm().Addr(), std.NewCoins(amount))
globalDeposits[caller] = amount
}

// Place the bid in the auction
if err := auction.AddBid(caller, amount); err != nil {
panic(err.Error())
}

return "Bid placed successfully"
}

// EndAuction handles ending an auction
// EndAuction handles ending an auction identified by its ID
func EndAuction(id int) string {
caller := std.GetOrigCaller()
if id >= len(auctionList) || id < 0 {
panic("Invalid auction ID")
}
auction := auctionList[id]
if err := auction.EndAuction(caller); err != nil {

// Call EndAuction to close the auction and get the winner and highest bid
winner, highestBid, err := auction.EndAuction()
if err != nil {
panic(err.Error())
}

// Handle fund transfers using the banker
banker := std.GetBanker(std.BankerTypeRealmSend)
pkgAddr := std.CurrentRealm().Addr()

// Refund all losing bidders
for bidder, deposit := range globalDeposits {
if bidder != winner {
banker.SendCoins(pkgAddr, bidder, std.NewCoins(deposit))
// Reset the deposit for the bidder
delete(globalDeposits, bidder)
}
}

// Transfer the highest bid to the auction owner
banker.SendCoins(pkgAddr, auction.Ownable.Owner(), std.NewCoins(highestBid))

// Clean up the winner's deposit
delete(globalDeposits, winner)

return "Auction ended successfully"
}

// Render renders the requested page
func Render(path string) string {
return router.Render(path)
}

// renderHomepage renders the homepage with links to different sections
// renderHomepage renders the homepage with links to different sections of the auction system
func renderHomepage(res *mux.ResponseWriter, req *mux.Request) {
var b bytes.Buffer
b.WriteString("<h1><center>Auction Home</center></h1>\n\n")
b.WriteString("<h2><a href='/r/demo/auction:create/'>Create New Auction</a></h2>\n\n")
b.WriteString("<h2><a href='/r/demo/auction:upcoming/'>Upcoming Auctions</a></h2>\n\n")
b.WriteString("<h2><a href='/r/demo/auction:ongoing/'>Ongoing Auctions</a></h2>\n\n")
res.Write(b.String())
res.Write(b.String()) // Write the homepage to the response
}

// renderCreateAuction renders the create auction page
// renderCreateAuction renders the auction creation form
func renderCreateAuction(res *mux.ResponseWriter, req *mux.Request) {
var b bytes.Buffer
b.WriteString("<html><body>")
Expand All @@ -92,45 +138,46 @@ func renderCreateAuction(res *mux.ResponseWriter, req *mux.Request) {
b.WriteString("<input type='submit' value='Create Auction'>\n")
b.WriteString("</form>\n")
b.WriteString("</body></html>")
res.Write(b.String())
res.Write(b.String()) // Write the form to the response
}

// renderUpcomingAuctions renders the upcoming auctions page
// renderUpcomingAuctions renders the list of upcoming auctions
func renderUpcomingAuctions(res *mux.ResponseWriter, req *mux.Request) {
var b bytes.Buffer
b.WriteString("<h1>Upcoming Auctions</h1>\n")

// Iterate over the list of auctions to find those with state 'upcoming'
for i, auc := range auctionList {
if auc.State == "upcoming" {
b.WriteString("\n\n")
b.WriteString("## " + auc.Title)
b.WriteString("\n\n")
b.WriteString("### Owner: " + auc.Owner.String() + "\n")
b.WriteString("## " + auc.Title + "\n")
b.WriteString("### Owner: " + auc.Ownable.Owner().String() + "\n")
b.WriteString("### Description: " + auc.Description + "\n\n")
b.WriteString("This auction starts on: " + auc.Begin.String() + " and ends on: " + auc.End.String() + "\n\n")
b.WriteString("### Starting Price: " + strconv.FormatUint(auc.Price, 10) + "\n")
b.WriteString("### Starting Price: " + auc.Price.String() + "\n")
b.WriteString("[View Auction](/auction/" + strconv.Itoa(i) + ")\n")
}
}
res.Write(b.String())
}

// renderOngoingAuctions renders the ongoing auctions page
// renderOngoingAuctions renders the list of ongoing auctions
func renderOngoingAuctions(res *mux.ResponseWriter, req *mux.Request) {
var b bytes.Buffer
b.WriteString("<h1>Ongoing Auctions</h1>\n")

// Iterate over the list of auctions to find those with state 'ongoing'
for i, auc := range auctionList {
if auc.State == "ongoing" {
b.WriteString("##" + auc.Title + "\n")
b.WriteString("### Owner: " + auc.Owner.String() + "\n")
b.WriteString("## " + auc.Title + "\n")
b.WriteString("### Owner: " + auc.Ownable.Owner().String() + "\n")
b.WriteString("### Description: " + auc.Description + "\n\n")
b.WriteString("This auction started on: " + auc.Begin.String() + " and ends on: " + auc.End.String() + "\n\n")
b.WriteString("### Current Price: " + strconv.FormatUint(auc.Price, 10) + "\n")
b.WriteString("### Current Price: " + auc.Price.String() + "\n")
b.WriteString("[View Auction](/auction/" + strconv.Itoa(i) + ")\n")
}
}
res.Write(b.String())
res.Write(b.String()) // Write the ongoing auctions to the response
}

// renderAuctionDetails renders the details of a specific auction
Expand All @@ -141,16 +188,17 @@ func renderAuctionDetails(res *mux.ResponseWriter, req *mux.Request) {
res.Write("Invalid auction ID")
return
}
auc := auctionList[id]
auc := auctionList[id] // Get the auction by ID

var b bytes.Buffer
b.WriteString("<h1>Auction Details</h1>\n")
b.WriteString("## " + auc.Title + "\n")
b.WriteString("### Owner: " + auc.Owner.String() + "\n")
b.WriteString("### Owner: " + auc.Ownable.Owner().String() + "\n")
b.WriteString("### Description: " + auc.Description + "\n\n")
b.WriteString("This auction starts on: " + auc.Begin.String() + " and ends on: " + auc.End.String() + "\n\n")
b.WriteString("### Current Price: " + strconv.FormatUint(auc.Price, 10) + "\n")
b.WriteString("### Current Price: " + auc.Price.String() + "\n")

// If the auction is ongoing, provide a form to place a bid
if auc.State == "ongoing" {
b.WriteString("<form action='/bid' method='post'>\n")
b.WriteString("Amount: <input type='number' name='amount'><br>\n")
Expand All @@ -159,5 +207,5 @@ func renderAuctionDetails(res *mux.ResponseWriter, req *mux.Request) {
b.WriteString("</form>\n")
}

res.Write(b.String())
res.Write(b.String()) // Write the auction details to the response
}
89 changes: 56 additions & 33 deletions api/r/auction/auction_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -5,81 +5,104 @@ import (
"strings"
"testing"
"time"
)

func setCurrentTime(t time.Time) {
currentTime = t
}
"gno.land/p/demo/testutils"
)

func resetCurrentTime() {
currentTime = time.Time{}
}

func TestAuction(t *testing.T) {
// Initialize the router and handlers

// Test the Auction realm functionality with global deposit management
func TestAuctionRealm(t *testing.T) {
// Simulate the admin creating an auction
adminAddr := std.Address("admin")
std.TestSetOrigCaller(adminAddr)

// Create an auction that starts almost immediately
begin := time.Now().Add(1 * time.Second).Unix() // Auction begins in 1 second
end := time.Now().Add(24 * time.Hour).Unix() // Auction ends in 24 hours
CreateAuction("Test Auction", "A simple test auction", begin, end, 100)
begin := time.Now().Add(1 * time.Second).Unix()
end := time.Now().Add(10 * time.Second).Unix()
minPrice := std.NewCoin("token", 100)

// Call the CreateAuction function
CreateAuction("Test Auction", "A simple test auction", begin, end, minPrice)

// Check if auction is in the upcoming section
std.TestSkipHeights(1) // Skip 1 block to simulate time passage
// Check if auction is listed under 'upcoming' section
std.TestSkipHeights(1) // Move forward by 1 block to allow processing
updateAuctionStates()

upcomingPage := Render("upcoming")
if !strings.Contains(upcomingPage, "Test Auction") {
t.Errorf("Auction should be listed in upcoming auctions")
t.Errorf("The auction should be listed in the 'upcoming' section")
}

// Simulate time passing to start the auction
std.TestSkipHeights(360) // Skip 360 blocks (1800 seconds or 30 minutes)
// Simulate time passing to move the auction to 'ongoing' state
std.TestSkipHeights(360) // Fast-forward to the start time of the auction
updateAuctionStates()

// Check if auction is in the ongoing section
// Check if auction is now listed under 'ongoing' section
ongoingPage := Render("ongoing")
if !strings.Contains(ongoingPage, "Test Auction") {
t.Errorf("Auction should be listed in ongoing auctions")
t.Errorf("The auction should be listed in the 'ongoing' section")
}

// Simulate users placing bids
user1 := std.Address("user1")
user2 := std.Address("user2")
user1 := testutils.TestAddress("user1")
user2 := testutils.TestAddress("user2")

// Set the caller to user1 and place a bid
// Place a bid from user1
std.TestSetOrigCaller(user1)
PlaceBid(0, 200)
AddBid(0, std.NewCoin("token", 200)) // Bid of 200 tokens

// Verify that the deposit was recorded in globalDeposits
if globalDeposits[user1].Amount != 200 {
t.Errorf("User1's deposit should be 200 tokens")
}

// Set the caller to user2 and place a bid
// Place a higher bid from user2
std.TestSetOrigCaller(user2)
PlaceBid(0, 300)
AddBid(0, std.NewCoin("token", 300)) // Bid of 300 tokens

// Verify that the deposit was recorded in globalDeposits
if globalDeposits[user2].Amount != 300 {
t.Errorf("User2's deposit should be 300 tokens")
}

// Check the details of the auction to verify bids
// Verify the auction details and the highest bid
auctionDetails := Render("auction/0")
if !strings.Contains(auctionDetails, "300") {
t.Errorf("Highest bid should be 300")
if !strings.Contains(auctionDetails, "300 token") {
t.Errorf("The highest bid should be 300 tokens")
}

// End the auction
// Simulate enough time passing for the auction to end
std.TestSkipHeights(720) // Skip enough blocks to simulate 5 seconds passing (for the auction to end)
updateAuctionStates()

// Simulate the admin ending the auction
std.TestSetOrigCaller(adminAddr)
EndAuction(0)

// Check if auction is in the closed state
std.TestSkipHeights(8640) // Skip 8640 blocks (43200 seconds or 12 hours)
updateAuctionStates()
// Verify that the auction has ended
auctionDetails = Render("auction/0")
if !strings.Contains(auctionDetails, "Auction ended") {
t.Errorf("Auction should be ended")
t.Errorf("The auction should be closed")
}

// Verify that user1 was refunded (as the losing bidder)
if _, exists := globalDeposits[user1]; exists {
t.Errorf("User1's deposit should have been refunded and removed from globalDeposits")
}

// Verify that user2's deposit was transferred to the auction owner
if _, exists := globalDeposits[user2]; exists {
t.Errorf("User2's deposit should have been transferred to the auction owner and removed from globalDeposits")
}

// Reset the global state after the test
resetCurrentTime()
}

// Update the auction states based on the current time

// Updates the auction states based on the current time
func updateAuctionStates() {
now := time.Now()
for _, auc := range auctionList {
Expand Down
Loading

0 comments on commit 0ef8ee9

Please sign in to comment.