Skip to content
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(PE-6656) - return initiated dutch auctions #92

Merged
merged 32 commits into from
Oct 23, 2024
Merged

Conversation

dtfiedler
Copy link
Collaborator

@dtfiedler dtfiedler commented Oct 9, 2024

Summary:
Introduces and Auction object class that is used to store auctions on the NameRegistry. We should look towards using more of these classes to leverage some better static analysis and type enforcement on functions. The class is responsible for computing prices based on the initialization details (baseFee, demandFactor, etc.). Auctions are stored as instances of this class, and returned as simple table (excluding function) so clients can compute prices themselves.

This initial implementation includes handler support for ANTs returning permabuy name registrations. In a separate PR we'll introduce Lease-Expiration-Intiated-Auctions where a name will go in to auction after the grace period has ended.

Checklist:

TODOs:

  • storing prices in auction state impacts the handler response (Data ends up being an empty table - not sure if this is a bug in AO or we are hitting a memory limit). it also costs ~600KiB to store those values in state, which could bloat our memory when several auctions are in process. we could consider just storing the settings of the auction pricing in state, and use that to calculate prices when necessary (still have to create the table, but response doesn't need to contain it). need to think more about this and what is necessary for clients to be able to showing pricing charts.
    • we've modified to store the auction settings on the auction object, and then clients will compute the price with various parameters

Here is the sequence diagram for a permabuy initiated auction
image

@codecov-commenter
Copy link

codecov-commenter commented Oct 9, 2024

Codecov Report

Attention: Patch coverage is 99.15966% with 1 line in your changes missing coverage. Please review.

Project coverage is 90.07%. Comparing base (9e6395b) to head (aec2ee4).

Files with missing lines Patch % Lines
src/arns.lua 98.68% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop      #92      +/-   ##
===========================================
+ Coverage    89.50%   90.07%   +0.57%     
===========================================
  Files            8        9       +1     
  Lines         1724     1834     +110     
===========================================
+ Hits          1543     1652     +109     
- Misses         181      182       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@dtfiedler dtfiedler changed the title Pe 6656 auctions PE-6656 - return initiated dutch auctions Oct 9, 2024
@arielmelendez
Copy link
Contributor

General request to add events for this flow when the PR is approaching readiness for review.

@dtfiedler dtfiedler marked this pull request as ready for review October 21, 2024 16:26
@dtfiedler dtfiedler changed the title PE-6656 - return initiated dutch auctions feat(PE-6656) - return initiated dutch auctions Oct 21, 2024
@@ -278,7 +279,7 @@ function arns.assertValidBuyRecord(name, years, purchaseType, processId)
assert(name:match("^%w") and name:match("%w$") and name:match("^[%w-]+$"), "Name pattern is invalid.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we have a util for this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eventually

Comment on lines +430 to +433
--- @param name string The name of the auction
--- @param timestamp number The timestamp to start the auction
--- @param initiator string The address of the initiator of the auction
--- @return Auction|nil The auction instance
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

Comment on lines +451 to +457
function arns.getAuction(name)
return NameRegistry.auctions[name]
end

function arns.getAuctions()
return NameRegistry.auctions or {}
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the or {} implies auctions could be undefined which would indicate that NameRegistry.auctions[name] could attempt to index a nil value

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can be, if we don't update the state manually

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we don't update the state manually
Is that the plan?

@dtfiedler dtfiedler merged commit 6d76e21 into develop Oct 23, 2024
@dtfiedler dtfiedler deleted the PE-6656-auctions branch October 23, 2024 15:25
end)
end)

describe("getAuction", function()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could add a test for a missing auction

then
local record = arns.getRecord(name)
local isPermabuy = record ~= nil and record.type == "permabuy"
local isActiveLease = record ~= nil and (record.endTimestamp or 0) + constants.gracePeriodMs > timestamp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: asserting the existence of the endTimestamp might be a little more robust.

@@ -48,18 +50,17 @@ function arns.buyRecord(name, purchaseType, years, from, timestamp, processId)
type = purchaseType,
undernameLimit = constants.DEFAULT_UNDERNAME_COUNT,
purchasePrice = totalRegistrationFee,
endTimestamp = purchaseType == "lease" and timestamp + constants.oneYearMs * years or nil,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lua's control flow in these scenarios freaks me out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I realize it's just a ternary but I wish they'd just use the same control characters as everyone else)

-- AUCTIONS

--- Creates an auction for a given name
--- @param name string The name of the auction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "the name being auctioned" otherwise this reads like it's an auction title

end

-- assert the bid is between auction start and end timestamps
if timestamp < auction.startTimestamp or timestamp > auction.endTimestamp then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should endTimestamp be inclusive?


-- assert the bid is between auction start and end timestamps
if timestamp < auction.startTimestamp or timestamp > auction.endTimestamp then
-- TODO: we should likely clean up the auction if it is outside of the time range
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prune should catch that IFF the endTimestamp is exclusive (i.e. prune would end an auction AT the end timestamp if they overlapped).

error("Bid timestamp is outside of auction start and end timestamps")
end
local requiredBid = auction:getPriceForAuctionAtTimestamp(timestamp, type, years)
local requiredOrBidAmount = bidAmount or requiredBid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the objective here? Let callers NOT supply a bidAmount?

processId = processId,
startTimestamp = timestamp,
endTimestamp = type == "lease" and timestamp + constants.oneYearMs * years or nil,
undernameLimit = constants.DEFAULT_UNDERNAME_COUNT,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the record had previously increased its undername limit, is that lost in transition here?

arns.removeAuction(name)
arns.addRecord(name, record)
-- make sure we tally name purchase given, even though only half goes to protocol
-- TODO: DO WE WANT TO TALLY THE ENTIRE AMOUNT OR JUST THE REWARD FOR THE PROTOCOL?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entire amount IMO

function arns.pruneAuctions(currentTimestamp)
local prunedAuctions = {}
for name, auction in pairs(arns.getAuctions()) do
if auction.endTimestamp <= currentTimestamp then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note here to make sure that end timestamp is handled consistently in various functions (inclusively or exclusively during bidding, pruning, etc.)

-- Default Auction Settings
AuctionSettings = {
durationMs = 60 * 1000 * 60 * 24 * 14, -- 14 days in milliseconds
decayRate = 0.000000000016847809193121693, -- 0.02037911 / durationMs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this is 27 digits after the decimal so keep that in mind if passing across process boundaries.

},
}
setmetatable(auction, self)
self.__index = self
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems there's some nuances to setting up the class this way (in the constructor) vs. separately. Will share chatGPT thread with you separately about it.

--- Computes the prices for the auction.
--- @param type string The type of auction
--- @param years number The number of years for calculation
--- @param intervalMs number The interval in milliseconds, must be at least 15 minutes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to double check where the 15 minutes is enforced.

@@ -676,7 +679,14 @@ addEventingHandler(ActionMap.BuyRecord, utils.hasMatchingTag("Action", ActionMap
ao.send({
Target = msg.From,
Tags = { Action = "Buy-Record-Notice", Name = msg.Tags.Name },
Data = json.encode(record),
Data = json.encode({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the material difference here?

local name = msg.Tags.Name
local processId = msg.From
local record = arns.getRecord(name)
local initiator = msg.Tags.Initiator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we be getting this from a Forwarded-For tag or similar that's provided by the MU during message forwarding?

end

-- we should be able to create the auction here
local status, auctionOrError = pcall(arns.createAuction, name, tonumber(msg.Timestamp), initiator)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's pretty much free to do these as eventingPcalls now since we wrapped all the handlers.

return
end

local auction = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this step necessary?

local timestamp = tonumber(msg.Tags.Timestamp or msg.Timestamp)
local type = msg.Tags.Type or "permabuy"
local years = msg.Tags.Years and tonumber(msg.Tags.Years) or nil
local intervalMs = msg.Tags["Price-Interval-Ms"] and tonumber(msg.Tags["Price-Interval-Ms"]) or 15 * 60 * 1000 -- 15 minute intervals by default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should enforce a minimum interval.

})
end)

addEventingHandler("auctionBid", utils.hasMatchingTag("Action", ActionMap.AuctionBid), function(msg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we move away from Buy-Record as the one true path for acquiring names?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants