Skip to content

Commit

Permalink
fix(primary-names): add patch for adding demand factor data to primar… (
Browse files Browse the repository at this point in the history
#408)

…y name requests

Steps to test:
1. Deploy new process against original module
```bash
❯ aos df-data-event-patch-2  --module=CWxzoe4IoNpFHiykadZWphZtLWybDF8ocNi7gmK6zCg --cu-url https://localhost:6363
          _____                   _______                   _____          
         /\    \                 /::\    \                 /\    \         
        /::\    \               /::::\    \               /::\    \        
       /::::\    \             /::::::\    \             /::::\    \       
      /::::::\    \           /::::::::\    \           /::::::\    \      
     /:::/\:::\    \         /:::/~~\:::\    \         /:::/\:::\    \     
    /:::/__\:::\    \       /:::/    \:::\    \       /:::/__\:::\    \    
   /::::\   \:::\    \     /:::/    / \:::\    \      \:::\   \:::\    \   
  /::::::\   \:::\    \   /:::/____/   \:::\____\   ___\:::\   \:::\    \  
 /:::/\:::\   \:::\    \ |:::|    |     |:::|    | /\   \:::\   \:::\    \ 
/:::/  \:::\   \:::\____\|:::|____|     |:::|    |/::\   \:::\   \:::\____\
\::/    \:::\  /:::/    / \:::\    \   /:::/    / \:::\   \:::\   \::/    /
 \/____/ \:::\/:::/    /   \:::\    \ /:::/    /   \:::\   \:::\   \/____/ 
          \::::::/    /     \:::\    /:::/    /     \:::\   \:::\    \     
           \::::/    /       \:::\__/:::/    /       \:::\   \:::\____\    
           /:::/    /         \::::::::/    /         \:::\  /:::/    /    
          /:::/    /           \::::::/    /           \:::\/:::/    /     
         /:::/    /             \::::/    /             \::::::/    /      
        /:::/    /               \::/____/               \::::/    /       
        \::/    /                 ~~                      \::/    /        
         \/____/                                           \/____/         
                                                                           
Welcome to AOS: Your operating system for AO, the decentralized open access supercomputer.
Type ".load-blueprint chat" to join the community chat and ask questions!
Using CU: https://localhost:6363

AOS Client Version: 2.0.4. 2024
Type "Ctrl-C" twice to exit

Your AOS process:  4_zQY9ZhaiiODch4r1YCUKQWq6-CQcs1TdJc3AQHzQY
```
2. Give balance to test address via AOS
```bash
[email protected][Inbox:1]> Balances[ao.id] = Balances[ao.id] - 1000000
{ "_e": 1, "Message-Id": "7zOtAV2QWo4aqSvtZnoNAt6xM22x48jSK-cKtfSUZ4s", "From": "TeWsA2tuo4aFnhWy-ZiP5t2FYXLisp3s4KagrX9LXEI", "Action": "Eval", "Timestamp": 1741618445018}
[email protected][Inbox:1]> Balances["trkCV2r8suvQYcEgJBKAspcx0oVFCrDOhd7aU-VXmDo"] = 1000000
{ "_e": 1, "Message-Id": "pwxdD4vpoJXOKa0crdyuEz-6KJr0w6L_vOOs43h3Ol0", "From": "TeWsA2tuo4aFnhWy-ZiP5t2FYXLisp3s4KagrX9LXEI", "Action": "Eval", "Timestamp": 1741618468668}
```
3. Load the patch file
```bash
[email protected][Inbox:1]> .load patches/2025-03-10-create-primary-name-request-demand-factor.lua
Loading...  patches/2025-03-10-create-primary-name-request-demand-factor.lua
```
4. Request a primary name with the test wallet
```bash
❯ ar.io request-primary-name --name ao --cu-url http://localhost:6363 --ario-process-id 4_zQY9ZhaiiODch4r1YCUKQWq6-CQcs1TdJc3AQHzQY --wallet-file test-wallet.json
✔ Are you sure you want to request the primary name ao? … yes

{
  "id": "5qpKoQvJYkfrrxd7JGLrqSe2eTS9zpMJONLYh-EnoAQ",
  "result": {
    "request": {
      "name": "ao",
      "endTimestamp": 1742223323568,
      "startTimestamp": 1741618523568
    },
    "fundingPlan": {
      "stakes": [],
      "address": "trkCV2r8suvQYcEgJBKAspcx0oVFCrDOhd7aU-VXmDo",
      "balance": 1000000,
      "shortfall": 0
    },
    "baseNameOwner": "HY021r2MQL9Zi0qSNFAQ9QRshIc2mNPYf65pZBP04cE",
    "demandFactor": {
      "trailingPeriodPurchases": [
        0,
        0,
        0,
        0,
        0,
        0,
        0
      ],
      "fees": [
        1000000000000,
        200000000000,
        20000000000,
        10000000000,
        2500000000,
        1500000000,
        800000000,
        500000000,
        400000000,
        350000000,
        300000000,
        250000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000,
        200000000
      ],
      "currentPeriod": 1,
      "trailingPeriodRevenues": [
        0,
        0,
        0,
        0,
        0,
        0,
        0
      ],
      "revenueThisPeriod": 1000000,
      "currentDemandFactor": 1,
      "purchasesThisPeriod": 1,
      "consecutivePeriodsWithMinDemandFactor": 0
    },
    "fundingResult": {
      "newWithdrawVaults": [],
      "totalFunded": 1000000
    }
  }
}
```
5. Review the event data [TODO]
  • Loading branch information
dtfiedler authored Mar 10, 2025
2 parents 522025d + 273b227 commit 3c857bf
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"test:unit:debug": "rm -rf coverage && mkdir -p coverage && DEBUG=true busted . && luacov",
"test:coverage": "rm -rf luacov-html && yarn test:unit && luacov --reporter html && open luacov-html/index.html",
"test:integration": "yarn build && node --test --experimental-wasm-memory64 **/*.test.mjs",
"patch:new": "node patch.mjs $1",
"monitor:down": "docker compose -f tests/monitor/docker-compose.test.yml down",
"monitor": "yarn monitor:down && node --test tests/monitor/monitor.test.mjs",
"monitor:devnet": "yarn monitor:down && ARIO_NETWORK_PROCESS_ID=GaQrvEMKBpkjofgnBi_B3IgIDmY_XYelVLB6GcRGrHc node --test tests/monitor/monitor.test.mjs",
Expand Down
26 changes: 26 additions & 0 deletions patch.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
*
* Script to create a new patch file.
*
* Usage: node patch.mjs <patch-name>
*
* Example: node patch.mjs add-demand-factor-data
*
*
*/
import fs from 'fs';

const date = new Date().toISOString().split('T')[0];

const patchName = process.argv[2];

if (!patchName) {
console.error('Patch name is required');
process.exit(1);
}

fs.mkdirSync('patches', { recursive: true });
fs.writeFileSync(
`patches/${date}-${patchName.replace(/ /g, '-').toLowerCase().trim()}.lua`,
'--[[\n\tPLACEHOLDER FOR PATCH DESCRIPTION\n\n\n\tReviewers: [PLACEHOLDER FOR REVIEWERS]\n]]--',
);
238 changes: 238 additions & 0 deletions patches/2025-03-10-create-primary-name-request-demand-factor.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
--[[
Adds demand factor data to the ioEvent for the requestPrimaryName handler.
NOTE: we have to include all the local functions in this patch as they are not available in global scope.
Reviewers: Dylan, Ariel, Atticus, Jon, Phil, Derek
]]
--
local utils = require(".src.utils")
local primaryNames = require(".src.primary_names")
local arns = require(".src.arns")
local gar = require(".src.gar")
local balances = require(".src.balances")
local demand = require(".src.demand")
local constants = require(".src.constants")

-- Update the primaryNames global function to return the demand factor data
primaryNames.createPrimaryNameRequest = function(name, initiator, timestamp, msgId, fundFrom)
fundFrom = fundFrom or "balance"

primaryNames.assertValidPrimaryName(name)

name = string.lower(name)
local baseName = utils.baseNameForName(name)

--- check the primary name request for the initiator does not already exist for the same name
--- this allows the caller to create a new request and pay the fee again, so long as it is for a different name
local existingRequest = primaryNames.getPrimaryNameRequest(initiator)
assert(
not existingRequest or existingRequest.name ~= name,
"Primary name request by '" .. initiator .. "' for '" .. name .. "' already exists"
)

--- check the primary name is not already owned
local primaryNameOwner = primaryNames.getAddressForPrimaryName(name)
assert(not primaryNameOwner, "Primary name is already owned")

local record = arns.getRecord(baseName)
assert(record, "ArNS record '" .. baseName .. "' does not exist")
assert(arns.recordIsActive(record, timestamp), "ArNS record '" .. baseName .. "' is not active")

local requestCost = arns.getTokenCost({
intent = "Primary-Name-Request",
name = name,
currentTimestamp = timestamp,
record = record,
})

local fundingPlan = gar.getFundingPlan(initiator, requestCost.tokenCost, fundFrom)
assert(fundingPlan and fundingPlan.shortfall == 0, "Insufficient balances")
local fundingResult = gar.applyFundingPlan(fundingPlan, msgId, timestamp)
assert(fundingResult.totalFunded == requestCost.tokenCost, "Funding plan application failed")

--- transfer the primary name cost from the initiator to the protocol balance
balances.increaseBalance(ao.id, requestCost.tokenCost)
demand.tallyNamePurchase(requestCost.tokenCost)

local request = {
name = name,
startTimestamp = timestamp,
endTimestamp = timestamp + constants.PRIMARY_NAME_REQUEST_DURATION_MS,
}

--- if the initiator is base name owner, then just set the primary name and return
local newPrimaryName
if record.processId == initiator then
newPrimaryName = primaryNames.setPrimaryNameFromRequest(initiator, request, timestamp)
else
-- otherwise store the request for asynchronous approval
PrimaryNames.requests[initiator] = request
primaryNames.scheduleNextPrimaryNamesPruning(request.endTimestamp)
end

return {
request = request,
newPrimaryName = newPrimaryName,
baseNameOwner = record.processId,
fundingPlan = fundingPlan,
fundingResult = fundingResult,
demandFactor = demand.getDemandFactorInfo(),
}
end

-- Now update main.lua to use the new function and add the demand factor data
local createPrimaryNameRequestHandlerIndex = utils.findInArray(Handlers.list, function(handler)
return handler.name == "requestPrimaryName"
end)

if not createPrimaryNameRequestHandlerIndex then
error("Failed to find requestPrimaryName handler")
end

local createPrimaryNameRequestHandler = Handlers.list[createPrimaryNameRequestHandlerIndex]
if not createPrimaryNameRequestHandler then
error("Failed to find requestPrimaryName handler")
end

local function Send(msg, response)
if msg.reply then
--- Reference: https://github.com/permaweb/aos/blob/main/blueprints/patch-legacy-reply.lua
msg.reply(response)
else
ao.send(response)
end
end

local function assertValidFundFrom(fundFrom)
if fundFrom == nil then
return
end
local validFundFrom = utils.createLookupTable({ "any", "balance", "stakes" })
assert(validFundFrom[fundFrom], "Invalid fund from type. Must be one of: any, balance, stakes")
end

local function addPrimaryNameCounts(ioEvent)
ioEvent:addField("Total-Primary-Names", utils.lengthOfTable(primaryNames.getUnsafePrimaryNames()))
ioEvent:addField("Total-Primary-Name-Requests", utils.lengthOfTable(primaryNames.getUnsafePrimaryNameRequests()))
end

local function adjustSuppliesForFundingPlan(fundingPlan, rewardForInitiator)
if not fundingPlan then
return
end
rewardForInitiator = rewardForInitiator or 0
local totalActiveStakesUsed = utils.reduce(fundingPlan.stakes, function(acc, _, stakeSpendingPlan)
return acc + stakeSpendingPlan.delegatedStake
end, 0)
local totalWithdrawStakesUsed = utils.reduce(fundingPlan.stakes, function(acc, _, stakeSpendingPlan)
return acc
+ utils.reduce(stakeSpendingPlan.vaults, function(acc2, _, vaultBalance)
return acc2 + vaultBalance
end, 0)
end, 0)
LastKnownStakedSupply = LastKnownStakedSupply - totalActiveStakesUsed
LastKnownWithdrawSupply = LastKnownWithdrawSupply - totalWithdrawStakesUsed
LastKnownCirculatingSupply = LastKnownCirculatingSupply - fundingPlan.balance + rewardForInitiator
end

local function addResultFundingPlanFields(ioEvent, result)
ioEvent:addFieldsWithPrefixIfExist(result.fundingPlan, "FP-", { "balance" })
local fundingPlanVaultsCount = 0
local fundingPlanStakesAmount = utils.reduce(
result.fundingPlan and result.fundingPlan.stakes or {},
function(acc, _, delegation)
return acc
+ delegation.delegatedStake
+ utils.reduce(delegation.vaults, function(acc2, _, vaultAmount)
fundingPlanVaultsCount = fundingPlanVaultsCount + 1
return acc2 + vaultAmount
end, 0)
end,
0
)
if fundingPlanStakesAmount > 0 then
ioEvent:addField("FP-Stakes-Amount", fundingPlanStakesAmount)
end
if fundingPlanVaultsCount > 0 then
ioEvent:addField("FP-Vaults-Count", fundingPlanVaultsCount)
end
local newWithdrawVaultsTallies = utils.reduce(
result.fundingResult and result.fundingResult.newWithdrawVaults or {},
function(acc, _, newWithdrawVault)
acc.totalBalance = acc.totalBalance
+ utils.reduce(newWithdrawVault, function(acc2, _, vault)
acc.count = acc.count + 1
return acc2 + vault.balance
end, 0)
return acc
end,
{ count = 0, totalBalance = 0 }
)
if newWithdrawVaultsTallies.count > 0 then
ioEvent:addField("New-Withdraw-Vaults-Count", newWithdrawVaultsTallies.count)
ioEvent:addField("New-Withdraw-Vaults-Total-Balance", newWithdrawVaultsTallies.totalBalance)
end
adjustSuppliesForFundingPlan(result.fundingPlan, result.returnedName and result.returnedName.rewardForInitiator)
end

--- @param ioEvent ARIOEvent
--- @param primaryNameResult CreatePrimaryNameResult|PrimaryNameRequestApproval
local function addPrimaryNameRequestData(ioEvent, primaryNameResult)
ioEvent:addFieldsIfExist(primaryNameResult, { "baseNameOwner" })
ioEvent:addFieldsIfExist(primaryNameResult.newPrimaryName, { "owner", "startTimestamp" })
ioEvent:addFieldsWithPrefixIfExist(primaryNameResult.request, "Request-", { "startTimestamp", "endTimestamp" })
addResultFundingPlanFields(ioEvent, primaryNameResult)
addPrimaryNameCounts(ioEvent)

-- add the demand factor data to the ioEvent
if primaryNameResult.demandFactor and type(primaryNameResult.demandFactor) == "table" then
ioEvent:addField("DF-Trailing-Period-Purchases", (primaryNameResult.demandFactor.trailingPeriodPurchases or {}))
ioEvent:addField("DF-Trailing-Period-Revenues", (primaryNameResult.demandFactor.trailingPeriodRevenues or {}))
ioEvent:addFieldsWithPrefixIfExist(primaryNameResult.demandFactor, "DF-", {
"currentPeriod",
"currentDemandFactor",
"consecutivePeriodsWithMinDemandFactor",
"revenueThisPeriod",
"purchasesThisPeriod",
})
end
end

-- Update the handler to use the new function and add the demand factor data
createPrimaryNameRequestHandler.handler = function(msg)
local fundFrom = msg.Tags["Fund-From"]
local name = msg.Tags.Name and string.lower(msg.Tags.Name) or nil
local initiator = msg.From
assert(name, "Name is required")
assert(initiator, "Initiator is required")
assertValidFundFrom(fundFrom)

local primaryNameResult = primaryNames.createPrimaryNameRequest(name, initiator, msg.Timestamp, msg.Id, fundFrom)

addPrimaryNameRequestData(msg.ioEvent, primaryNameResult)

--- if the from is the new owner, then send an approved notice to the from
if primaryNameResult.newPrimaryName then
Send(msg, {
Target = msg.From,
Action = ActionMap.ApprovePrimaryNameRequest .. "-Notice",
Data = json.encode(primaryNameResult),
})
return
end

if primaryNameResult.request then
--- send a notice to the msg.From, and the base name owner
Send(msg, {
Target = msg.From,
Action = ActionMap.PrimaryNameRequest .. "-Notice",
Data = json.encode(primaryNameResult),
})
Send(msg, {
Target = primaryNameResult.baseNameOwner,
Action = ActionMap.PrimaryNameRequest .. "-Notice",
Data = json.encode(primaryNameResult),
})
end
end
5 changes: 5 additions & 0 deletions spec/primary_names_spec.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local primaryNames = require("primary_names")
local utils = require("utils")
local demand = require("demand")

describe("Primary Names", function()
before_each(function()
Expand Down Expand Up @@ -308,6 +309,7 @@ describe("Primary Names", function()
1234567890,
"test-msg-id"
)
local demandFactor = demand.getDemandFactorInfo()
assert.are.same({
request = {
name = "test",
Expand All @@ -325,6 +327,7 @@ describe("Primary Names", function()
newWithdrawVaults = {},
totalFunded = 200000,
},
demandFactor = demandFactor,
}, primaryNameRequest)
assert.are.equal(9800000, _G.Balances["user-requesting-primary-name"])
assert.are.equal(200000, _G.Balances[ao.id])
Expand Down Expand Up @@ -361,6 +364,7 @@ describe("Primary Names", function()
1234567890,
"test-msg-id"
)
local demandFactor = demand.getDemandFactorInfo()
assert.are.same({
request = {
name = primaryName,
Expand All @@ -378,6 +382,7 @@ describe("Primary Names", function()
newWithdrawVaults = {},
totalFunded = 200000,
},
demandFactor = demandFactor,
}, primaryNameRequest)
assert.are.equal(9800000, _G.Balances["user-requesting-primary-name"])
assert.are.equal(200000, _G.Balances[ao.id])
Expand Down
13 changes: 13 additions & 0 deletions src/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,19 @@ local function addPrimaryNameRequestData(ioEvent, primaryNameResult)
ioEvent:addFieldsWithPrefixIfExist(primaryNameResult.request, "Request-", { "startTimestamp", "endTimestamp" })
addResultFundingPlanFields(ioEvent, primaryNameResult)
addPrimaryNameCounts(ioEvent)

-- demand factor data
if primaryNameResult.demandFactor and type(primaryNameResult.demandFactor) == "table" then
ioEvent:addField("DF-Trailing-Period-Purchases", (primaryNameResult.demandFactor.trailingPeriodPurchases or {}))
ioEvent:addField("DF-Trailing-Period-Revenues", (primaryNameResult.demandFactor.trailingPeriodRevenues or {}))
ioEvent:addFieldsWithPrefixIfExist(primaryNameResult.demandFactor, "DF-", {
"currentPeriod",
"currentDemandFactor",
"consecutivePeriodsWithMinDemandFactor",
"revenueThisPeriod",
"purchasesThisPeriod",
})
end
end

local function assertValueBytesLowerThan(value, remainingBytes, tablesSeen)
Expand Down
4 changes: 3 additions & 1 deletion src/primary_names.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ local primaryNames = {}
--- @field baseNameOwner WalletAddress
--- @field fundingPlan table
--- @field fundingResult table

--- @field demandFactor table
---
-- NOTE: lua 5.3 has limited regex support, particularly for lookaheads and negative lookaheads or use of {n}
---@param name string
---@description Asserts that the provided name is a valid undername
Expand Down Expand Up @@ -154,6 +155,7 @@ function primaryNames.createPrimaryNameRequest(name, initiator, timestamp, msgId
baseNameOwner = record.processId,
fundingPlan = fundingPlan,
fundingResult = fundingResult,
demandFactor = demand.getDemandFactorInfo(),
}
end

Expand Down
Loading

0 comments on commit 3c857bf

Please sign in to comment.