Skip to content

Commit

Permalink
feat(arns): add demand factor to arns name purchases and name extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
dtfiedler committed May 13, 2024
1 parent 89f68ab commit 0be3e66
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 48 deletions.
60 changes: 60 additions & 0 deletions contract/spec/arns_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe("arns", function()
before_each(function()
arns.records = {}
arns.reserved = {}
arns.fees = constants.genesisFees
token.balances = {
Bob = 5000000,
}
Expand Down Expand Up @@ -42,6 +43,33 @@ describe("arns", function()
}, token.balances)
end)

it('defaults to 1 year and lease when not provided', function ()
local status, result = pcall(arns.buyRecord, "test-name", nil, nil, "Bob", false, timestamp, testProcessId)
assert.is_true(status)
assert.are.same({
purchasePrice = 1500,
type = "lease",
undernameCount = 10,
processId = testProcessId,
startTimestamp = 0,
endTimestamp = timestamp + constants.MS_IN_A_YEAR,
}, result)
assert.are.same({
["test-name"] = {
purchasePrice = 1500,
type = "lease",
undernameCount = 10,
processId = testProcessId,
startTimestamp = 0,
endTimestamp = timestamp + constants.MS_IN_A_YEAR,
},
}, arns.records)
assert.are.same({
["Bob"] = 4998500,
[_G.ao.id] = 1500,
}, token.balances)
end)

it(
"should validate a permabuy request and add the record to global state and deduct balance from caller",
function()
Expand Down Expand Up @@ -170,5 +198,37 @@ describe("arns", function()
assert.is_false(status)
assert.match("Name is not registered", error)
end)

it('should allow extension for existing lease up to 5 years', function()
arns.records["test-name"] = {
-- 1 year lease
endTimestamp = timestamp + constants.MS_IN_A_YEAR,
processId = testProcessId,
purchasePrice = 1500,
startTimestamp = 0,
type = "lease",
undernameCount = 10,
}
local status, result = pcall(arns.extendLease, "Bob", "test-name", 4, timestamp)
assert.is_true(status)
assert.are.same({
endTimestamp = timestamp + constants.MS_IN_A_YEAR * 5,
processId = testProcessId,
purchasePrice = 1500,
startTimestamp = 0,
type = "lease",
undernameCount = 10,
}, result)
assert.are.same({
["test-name"] = {
endTimestamp = timestamp + constants.MS_IN_A_YEAR * 5,
processId = testProcessId,
purchasePrice = 1500,
startTimestamp = 0,
type = "lease",
undernameCount = 10,
},
}, arns.records)
end)
end)
end)
22 changes: 17 additions & 5 deletions contract/src/arns.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
local utils = require("utils")
local constants = require("constants")
local token = Token or require("token")
local demand = Demand or require("demand")
local arns = {
reserved = {},
records = {},
auctions = {},
fees = constants.genesisFees,
}
function arns.buyRecord(name, purchaseType, years, from, auction, timestamp, processId)
-- don't catch, let the caller handle the error
Expand All @@ -19,7 +21,9 @@ function arns.buyRecord(name, purchaseType, years, from, auction, timestamp, pro
years = 1 -- set to 1 year by default
end

local totalRegistrationFee = utils.calculateRegistrationFee(purchaseType, name, years)
local baseRegistrionFee = arns.fees[#name]

local totalRegistrationFee = utils.calculateRegistrationFee(purchaseType, baseRegistrionFee, years, demand.getDemandFactor())

if token.getBalance(from) < totalRegistrationFee then
error("Insufficient funds")
Expand Down Expand Up @@ -66,13 +70,20 @@ function arns.extendLease(from, name, years, timestamp)
local record = arns.getRecord(name)
-- throw error if invalid
utils.assertValidExtendLease(record, timestamp, years)

local totalExtensionFee = utils.calculateExtensionFee(name, years, record.type)
local baseRegistrionFee = arns.fees[#name]
local totalExtensionFee = utils.calculateExtensionFee(baseRegistrionFee, years, demand.getDemandFactor())
-- Transfer tokens to the protocol balance
token.transfer(ao.id, from, totalExtensionFee)

arns.records[name].endTimestamp = record.endTimestamp + constants.MS_IN_A_YEAR * years
return Records[name]
return arns.records[name]
end

function arns.calculateExtensionFee(name, years, purchaseType)
local record = arns.getRecord(name)
local yearsRemaining = utils.calculateYearsBetweenTimestamps(record.endTimestamp, timestamp)
local extensionFee = utils.calculateUndernameCost(name, years, purchaseType, yearsRemaining)
return extensionFee
end

function arns.increaseUndernameCount(from, name, qty, timestamp)
Expand All @@ -93,7 +104,8 @@ function arns.increaseUndernameCount(from, name, qty, timestamp)
end

local existingUndernames = record.undernameCount
local additionalUndernameCost = utils.calculateUndernameCost(name, qty, record.type, yearsRemaining)
local baseRegistrionFee = arns.fees[#name]
local additionalUndernameCost = utils.calculateUndernameCost(baseRegistrionFee, qty, record.type, yearsRemaining, demand.getDemandFactor())

-- Transfer tokens to the protocol balance
token.transfer(ao.id, from, additionalUndernameCost)
Expand Down
3 changes: 2 additions & 1 deletion contract/src/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ constants.ARNS_MAX_UNDERNAME_MESSAGE = "Name has reached undername limit of 1000
constants.MAX_ALLOWED_UNDERNAMES = 10000
constants.UNDERNAME_LEASE_FEE_PERCENTAGE = 0.001
constants.UNDERNAME_PERMABUY_FEE_PERCENTAGE = 0.005
constants.MS_IN_GRACE_PERIOD = 3 * 7 * 24 * 60 * 60 * 1000
constants.gracePeriodMs = 3 * 7 * 24 * 60 * 60 * 1000
constants.maxLeaseLengthYears = 5

-- DEMAND
constants.DEMAND_SETTINGS = {
Expand Down
67 changes: 25 additions & 42 deletions contract/src/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -185,63 +185,46 @@ function utils.walletHasSufficientBalance(wallet, quantity)
return Balances[wallet] ~= nil and Balances[wallet] >= quantity
end

function utils.calculateLeaseFee(name, years)
-- Initial cost to register a name
-- TODO: Harden the types here to make fees[name.length] an error
local initialNamePurchaseFee = constants.genesisFees[string.len(name)]

-- total cost to purchase name (no demand factor)
return (initialNamePurchaseFee + utils.calculateAnnualRenewalFee(name, years))
function utils.calculateLeaseFee(baseFee, years, demandFactor)
local annualRegistrionFee = utils.calculateAnnualRenewalFee(baseFee, years)
local totalLeaseCost = baseFee + annualRegistrionFee
return demandFactor * totalLeaseCost
end

function utils.calculateAnnualRenewalFee(name, years)
-- Determine annual registration price of name
local initialNamePurchaseFee = constants.genesisFees[string.len(name)]

-- Annual fee is specific % of initial purchase cost
local nameAnnualRegistrationFee = initialNamePurchaseFee * constants.ANNUAL_PERCENTAGE_FEE

function utils.calculateAnnualRenewalFee(baseFee, years)
local nameAnnualRegistrationFee = baseFee * constants.ANNUAL_PERCENTAGE_FEE
local totalAnnualRenewalCost = nameAnnualRegistrationFee * years

return totalAnnualRenewalCost
end

function utils.calculatePermabuyFee(name)
-- genesis price
local initialNamePurchaseFee = constants.genesisFees[string.len(name)]

-- calculate the annual fee for the name for default of 10 years
local permabuyPrice =
-- No demand factor
initialNamePurchaseFee -- total renewal cost pegged to 10 years to purchase name
+ utils.calculateAnnualRenewalFee(name, constants.PERMABUY_LEASE_FEE_LENGTH)
return permabuyPrice
function utils.calculatePermabuyFee(baseFee, demandFactor)
local permabuyPrice = baseFee + utils.calculateAnnualRenewalFee(baseFee, constants.PERMABUY_LEASE_FEE_LENGTH)
return demandFactor * permabuyPrice
end

function utils.calculateRegistrationFee(purchaseType, name, years)
function utils.calculateRegistrationFee(purchaseType, baseFee, years, demandFactor)
if purchaseType == "lease" then
return utils.calculateLeaseFee(name, years)
return utils.calculateLeaseFee(baseFee, years, demandFactor)
elseif purchaseType == "permabuy" then
return utils.calculatePermabuyFee(name)
return utils.calculatePermabuyFee(baseFee, demandFactor)
end
end

function utils.calculateUndernameCost(name, increaseQty, registrationType, years)
local initialNameFee = constants.genesisFees[string.len(name)] -- Get the fee based on the length of the name
if initialNameFee == nil then
-- Handle the case where there is no fee for the given name length
return 0
end

function utils.calculateUndernameCost(baseFee, increaseQty, registrationType, years, demandFactor)
local undernamePercentageFee = 0
if registrationType == "lease" then
undernamePercentageFee = constants.UNDERNAME_LEASE_FEE_PERCENTAGE
elseif registrationType == "permabuy" then
undernamePercentageFee = constants.UNDERNAME_PERMABUY_FEE_PERCENTAGE
end

local totalFeeForQtyAndYears = initialNameFee * undernamePercentageFee * increaseQty * years
return totalFeeForQtyAndYears
local totalFeeForQtyAndYears = baseFee * undernamePercentageFee * increaseQty * years
return demandFactor * totalFeeForQtyAndYears
end

function utils.calculateExtensionFee(baseFee, years, demandFactor)
local extensionFee = utils.calculateAnnualRenewalFee(baseFee, years)
return demandFactor * extensionFee
end

function utils.isLeaseRecord(record)
Expand All @@ -265,7 +248,7 @@ function utils.isNameInGracePeriod(record, currentTimestamp)
if not record or not record.endTimestamp then
return false
end -- if it has no timestamp, it is a permabuy
if (utils.ensureMilliseconds(record.endTimestamp) + constants.MS_IN_GRACE_PERIOD) < currentTimestamp then
if (utils.ensureMilliseconds(record.endTimestamp) + constants.gracePeriodMs) < currentTimestamp then
return false
end
return true
Expand Down Expand Up @@ -348,8 +331,8 @@ end

function utils.assertValidExtendLease(record, currentTimestamp, years)
utils.assertAllowedNameModification(record, currentTimestamp)

if years > utils.getMaxAllowedYearsExtensionForRecord(record, currentTimestamp) then
local maxAllowedYears = utils.getMaxAllowedYearsExtensionForRecord(record, currentTimestamp)
if years > maxAllowedYears then
error("Invalid number of years for record extension")
end
end
Expand All @@ -360,12 +343,12 @@ function utils.getMaxAllowedYearsExtensionForRecord(record, currentTimestamp)
end

-- if expired return 0 because it cannot be extended and must be re-bought
if currentTimestamp > record.endTimestamp + constants.SECONDS_IN_GRACE_PERIOD then
if currentTimestamp > (record.endTimestamp + constants.gracePeriodMs) then
return 0
end

if utils.isNameInGracePeriod(record, currentTimestamp) then
return constants.ARNS_LEASE_LENGTH_MAX_YEARS
return constants.maxLeaseLengthYears
end

-- TODO: should we put this as the ceiling? or should we allow people to extend as soon as it is purchased
Expand Down

0 comments on commit 0be3e66

Please sign in to comment.