diff --git a/src/balances.lua b/src/balances.lua index 8513c87..b89db63 100644 --- a/src/balances.lua +++ b/src/balances.lua @@ -52,8 +52,8 @@ function balances.reduceBalance(target, qty) end --- Increases the balance of an address ----@param target string The address to increase balance for ----@param qty number The amount to increase by (must be integer) +--- @param target string The address to increase balance for +--- @param qty number The amount to increase by (must be integer) function balances.increaseBalance(target, qty) assert(utils.isInteger(qty), debug.traceback("Quantity must be an integer: " .. qty)) local prevBalance = balances.getBalance(target) or 0 @@ -61,11 +61,11 @@ function balances.increaseBalance(target, qty) end --- Gets paginated list of all balances ----@param cursor string|nil The address to start from ----@param limit number|nil Max number of results to return ----@param sortBy string|nil Field to sort by ----@param sortOrder string|nil "asc" or "desc" sort direction ----@return table Array of {address, balance} objects +--- @param cursor string|nil The address to start from +--- @param limit number|nil Max number of results to return +--- @param sortBy string|nil Field to sort by +--- @param sortOrder string|nil "asc" or "desc" sort direction +--- @return table Array of {address, balance} objects function balances.getPaginatedBalances(cursor, limit, sortBy, sortOrder) local allBalances = balances.getBalances() local balancesArray = {} @@ -80,6 +80,10 @@ function balances.getPaginatedBalances(cursor, limit, sortBy, sortOrder) return utils.paginateTableWithCursor(balancesArray, cursor, cursorField, limit, sortBy, sortOrder) end +--- Checks if a wallet has a sufficient balance +--- @param wallet string The address of the wallet +--- @param quantity number The amount to check against the balance +--- @return boolean True if the wallet has a sufficient balance, false otherwise function balances.walletHasSufficientBalance(wallet, quantity) return Balances[wallet] ~= nil and Balances[wallet] >= quantity end diff --git a/src/demand.lua b/src/demand.lua index 4b9bfec..c16a533 100644 --- a/src/demand.lua +++ b/src/demand.lua @@ -27,6 +27,8 @@ DemandFactorSettings = DemandFactorSettings criteria = "revenue", } +--- Tally a name purchase +--- @param qty number The quantity of the purchase function demand.tallyNamePurchase(qty) demand.incrementPurchasesThisPeriodRevenue(1) demand.incrementRevenueThisPeriod(qty) @@ -39,6 +41,8 @@ function demand.baseFeeForNameLength(nameLength) return demand.getFees()[nameLength] end +--- Gets the moving average of trailing purchase counts +--- @return number The moving average of trailing purchase counts function demand.mvgAvgTrailingPurchaseCounts() local sum = 0 local trailingPeriodPurchases = demand.getTrailingPeriodPurchases() @@ -48,6 +52,8 @@ function demand.mvgAvgTrailingPurchaseCounts() return sum / #trailingPeriodPurchases end +--- Gets the moving average of trailing revenues +--- @return number The moving average of trailing revenues function demand.mvgAvgTrailingRevenues() local sum = 0 local trailingPeriodRevenues = demand.getTrailingPeriodRevenues() @@ -57,6 +63,8 @@ function demand.mvgAvgTrailingRevenues() return sum / #trailingPeriodRevenues end +--- Checks if the demand is increasing +--- @return boolean True if the demand is increasing, false otherwise function demand.isDemandIncreasing() local settings = demand.getSettings() @@ -78,7 +86,9 @@ function demand.isDemandIncreasing() end end --- update at the end of the demand if the current timestamp results in a period greater than our current state +--- Checks if the demand should update the demand factor +--- @param currentTimestamp number The current timestamp +--- @return boolean True if the demand should update the demand factor, false otherwise function demand.shouldUpdateDemandFactor(currentTimestamp) local settings = demand.getSettings() @@ -92,22 +102,27 @@ function demand.shouldUpdateDemandFactor(currentTimestamp) return calculatedPeriod > demand.getCurrentPeriod() end +--- Gets the demand factor info +--- @return table The demand factor info function demand.getDemandFactorInfo() return utils.deepCopy(DemandFactor) end +--- Updates the demand factor +--- @param timestamp number The current timestamp +--- @return number | nil The updated demand factor or nil if it should not be updated function demand.updateDemandFactor(timestamp) if not demand.shouldUpdateDemandFactor(timestamp) then print("Not updating demand factor") - return -- silently return + return demand.getDemandFactor() end local settings = demand.getSettings() -- check that we have settings - if not settings then + if not demand.shouldUpdateDemandFactor(timestamp) or not settings then print("No settings found") - return + return demand.getDemandFactor() end if demand.isDemandIncreasing() then @@ -143,6 +158,9 @@ function demand.updateDemandFactor(timestamp) return demand.getDemandFactor() end +--- Updates the fees +--- @param multiplier number The multiplier for the fees +--- @return table The updated fees function demand.updateFees(multiplier) local currentFees = demand.getFees() -- update all fees multiply them by the demand factor minimim @@ -150,52 +168,73 @@ function demand.updateFees(multiplier) local updatedFee = fee * multiplier DemandFactor.fees[nameLength] = updatedFee end + return demand.getFees() end +--- Gets the demand factor +--- @return number The demand factor function demand.getDemandFactor() local demandFactor = utils.deepCopy(DemandFactor) return demandFactor and demandFactor.currentDemandFactor or 1 end +--- Gets the current period revenue +--- @return number The current period revenue function demand.getCurrentPeriodRevenue() local demandFactor = utils.deepCopy(DemandFactor) return demandFactor and demandFactor.revenueThisPeriod or 0 end +--- Gets the current period purchases +--- @return number The current period purchases function demand.getCurrentPeriodPurchases() local demandFactor = utils.deepCopy(DemandFactor) return demandFactor and demandFactor.purchasesThisPeriod or 0 end +--- Gets the trailing period purchases +--- @return table The trailing period purchases function demand.getTrailingPeriodPurchases() local demandFactor = utils.deepCopy(DemandFactor) return demandFactor and demandFactor.trailingPeriodPurchases or { 0, 0, 0, 0, 0, 0, 0 } end +--- Gets the trailing period revenues +--- @return table The trailing period revenues function demand.getTrailingPeriodRevenues() local demandFactor = utils.deepCopy(DemandFactor) return demandFactor and demandFactor.trailingPeriodRevenues or { 0, 0, 0, 0, 0, 0, 0 } end +--- Gets the fees +--- @return table The fees function demand.getFees() local demandFactor = utils.deepCopy(DemandFactor) return demandFactor and demandFactor.fees or {} end +--- Gets the settings +--- @return table The settings function demand.getSettings() return utils.deepCopy(DemandFactorSettings) end +--- Gets the consecutive periods with minimum demand factor +--- @return number The consecutive periods with minimum demand factor function demand.getConsecutivePeriodsWithMinDemandFactor() local demandFactor = utils.deepCopy(DemandFactor) return demandFactor and demandFactor.consecutivePeriodsWithMinDemandFactor or 0 end +--- Gets the current period +--- @return number The current period function demand.getCurrentPeriod() local demandFactor = utils.deepCopy(DemandFactor) return demandFactor and demandFactor.currentPeriod or 1 end +--- Updates the settings +--- @param settings table The settings function demand.updateSettings(settings) if not settings then return @@ -203,18 +242,26 @@ function demand.updateSettings(settings) DemandFactorSettings = settings end +--- Updates the start timestamp +--- @param timestamp number The timestamp function demand.updateStartTimestamp(timestamp) DemandFactorSettings.periodZeroStartTimestamp = timestamp end +--- Updates the current period +--- @param period number The period function demand.updateCurrentPeriod(period) DemandFactor.currentPeriod = period end +--- Sets the demand factor +--- @param demandFactor number The demand factor function demand.setDemandFactor(demandFactor) DemandFactor.currentDemandFactor = demandFactor end +--- Gets the period index +--- @return number The period index function demand.getPeriodIndex() local currentPeriod = demand.getCurrentPeriod() local settings = demand.getSettings() @@ -225,44 +272,59 @@ function demand.getPeriodIndex() return (currentPeriod % settings.movingAvgPeriodCount) + 1 -- has to be + 1 to avoid zero index end +--- Updates the trailing period purchases function demand.updateTrailingPeriodPurchases() local periodIndex = demand.getPeriodIndex() DemandFactor.trailingPeriodPurchases[periodIndex] = demand.getCurrentPeriodPurchases() end +--- Updates the trailing period revenues function demand.updateTrailingPeriodRevenues() local periodIndex = demand.getPeriodIndex() DemandFactor.trailingPeriodRevenues[periodIndex] = demand.getCurrentPeriodRevenue() end +--- Resets the purchases this period function demand.resetPurchasesThisPeriod() DemandFactor.purchasesThisPeriod = 0 end +--- Resets the revenue this period function demand.resetRevenueThisPeriod() DemandFactor.revenueThisPeriod = 0 end +--- Increments the purchases this period +--- @param count number The count to increment function demand.incrementPurchasesThisPeriodRevenue(count) DemandFactor.purchasesThisPeriod = DemandFactor.purchasesThisPeriod + count end +--- Increments the revenue this period +--- @param revenue number The revenue to increment function demand.incrementRevenueThisPeriod(revenue) DemandFactor.revenueThisPeriod = DemandFactor.revenueThisPeriod + revenue end +--- Updates the revenue this period +--- @param revenue number The revenue to update function demand.updateRevenueThisPeriod(revenue) DemandFactor.revenueThisPeriod = revenue end +--- Increments the current period +--- @param count number The count to increment function demand.incrementCurrentPeriod(count) DemandFactor.currentPeriod = DemandFactor.currentPeriod + count end +--- Resets the consecutive periods with minimum demand factor function demand.resetConsecutivePeriodsWithMinimumDemandFactor() DemandFactor.consecutivePeriodsWithMinDemandFactor = 0 end +--- Increments the consecutive periods with minimum demand factor +--- @param count number The count to increment function demand.incrementConsecutivePeriodsWithMinDemandFactor(count) DemandFactor.consecutivePeriodsWithMinDemandFactor = DemandFactor.consecutivePeriodsWithMinDemandFactor + count end diff --git a/src/tick.lua b/src/tick.lua index c742947..9e4d03e 100644 --- a/src/tick.lua +++ b/src/tick.lua @@ -3,6 +3,11 @@ local arns = require("arns") local gar = require("gar") local vaults = require("vaults") local epochs = require("epochs") + +--- Prunes the state +--- @param timestamp number The timestamp +--- @param msgId string The message ID +--- @return table The pruned records, auctions, reserved names, vaults, gateways, and epochs function tick.pruneState(timestamp, msgId) local prunedRecords = arns.pruneRecords(timestamp) local prunedAuctions = arns.pruneAuctions(timestamp) diff --git a/src/utils.lua b/src/utils.lua index 34c15af..20f8b47 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -25,6 +25,9 @@ function utils.roundToPrecision(number, precision) return math.floor(number * (10 ^ precision) + 0.5) / (10 ^ precision) end +--- Sums the values of a table +--- @param tbl table The table to sum +--- @return number The sum of the table values function utils.sumTableValues(tbl) local sum = 0 for _, value in pairs(tbl) do @@ -33,6 +36,12 @@ function utils.sumTableValues(tbl) return sum end +--- Slices a table +--- @param tbl table The table to slice +--- @param first number The first index to slice from +--- @param last number The last index to slice to +--- @param step number The step to slice by +--- @return table The sliced table function utils.slice(tbl, first, last, step) local sliced = {} @@ -43,6 +52,9 @@ function utils.slice(tbl, first, last, step) return sliced end +--- Parses the pagination tags from a message +--- @param msg table The message provided to a handler (see ao docs for more info) +--- @return table The pagination tags function utils.parsePaginationTags(msg) local cursor = msg.Tags.Cursor local limit = tonumber(msg.Tags["Limit"]) or 100 @@ -56,8 +68,13 @@ function utils.parsePaginationTags(msg) } end +--- Sorts a table by a given field +--- @param prevTable table The table to sort +--- @param field string The field to sort by +--- @param order string The order to sort by ("asc" or "desc") +--- @return table The sorted table function utils.sortTableByField(prevTable, field, order) - local tableCopy = utils.deepCopy(prevTable) + local tableCopy = utils.deepCopy(prevTable) or {} if order ~= "asc" and order ~= "desc" then error("Invalid sort order") @@ -89,6 +106,14 @@ function utils.sortTableByField(prevTable, field, order) return tableCopy end +--- Paginate a table with a cursor +--- @param tableArray table The table to paginate +--- @param cursor string The cursor to paginate from +--- @param cursorField string The field to use as the cursor +--- @param limit number The limit of items to return +--- @param sortBy string The field to sort by +--- @param sortOrder string The order to sort by ("asc" or "desc") +--- @return table The paginated table function utils.paginateTableWithCursor(tableArray, cursor, cursorField, limit, sortBy, sortOrder) local sortedArray = utils.sortTableByField(tableArray, sortBy, sortOrder) @@ -138,24 +163,32 @@ function utils.paginateTableWithCursor(tableArray, cursor, cursorField, limit, s } end +--- Checks if an address is a valid Arweave address +--- @param address string The address to check +--- @return boolean Whether the address is a valid Arweave address function utils.isValidArweaveAddress(address) return type(address) == "string" and #address == 43 and string.match(address, "^[%w-_]+$") ~= nil end +--- Checks if an address is a valid Ethereum address +--- @param address string The address to check +--- @return boolean Whether the address is a valid Ethereum address function utils.isValidEthAddress(address) return type(address) == "string" and #address == 42 and string.match(address, "^0x[%x]+$") ~= nil end ---- Checks if an address is a valid base64url +--- Checks if an address is a valid AO address --- @param url string|nil The address to check ---- @return boolean Whether the address is a valid base64url +--- @return boolean Whether the address is a valid AO address function utils.isValidAOAddress(url) return url and (utils.isValidArweaveAddress(url) or utils.isValidEthAddress(url)) or false end --- Convert address to EIP-55 checksum format --- assumes address has been validated as a valid Ethereum address (see utils.isValidEthAddress) --- Reference: https://eips.ethereum.org/EIPS/eip-55 +--- Converts an address to EIP-55 checksum format +--- Assumes address has been validated as a valid Ethereum address (see utils.isValidEthAddress) +--- Reference: https://eips.ethereum.org/EIPS/eip-55 +--- @param address string The address to convert +--- @return string The EIP-55 checksum formatted address function utils.formatEIP55Address(address) local hex = string.lower(string.sub(address, 3)) @@ -177,6 +210,9 @@ function utils.formatEIP55Address(address) return checksumAddress end +--- Formats an address to EIP-55 checksum format if it is a valid Ethereum address +--- @param address string The address to format +--- @return string The EIP-55 checksum formatted address function utils.formatAddress(address) if utils.isValidEthAddress(address) then return utils.formatEIP55Address(address) @@ -184,6 +220,9 @@ function utils.formatAddress(address) return address end +--- Safely decodes a JSON string +--- @param jsonString string The JSON string to decode +--- @return table|nil The decoded JSON or nil if the string is nil or the decoding fails function utils.safeDecodeJson(jsonString) if not jsonString then return nil @@ -196,6 +235,10 @@ function utils.safeDecodeJson(jsonString) return result end +--- Finds an element in an array that matches a predicate +--- @param array table The array to search +--- @param predicate function The predicate to match +--- @return number|nil The index of the found element or nil if the element is not found function utils.findInArray(array, predicate) for i = 1, #array do if predicate(array[i]) then @@ -206,8 +249,8 @@ function utils.findInArray(array, predicate) end --- Deep copies a table ----@param original table The table to copy ----@return table|nil The deep copy of the table or nil if the original is nil +--- @param original table The table to copy +--- @return table|nil The deep copy of the table or nil if the original is nil function utils.deepCopy(original) if not original then return nil @@ -228,6 +271,9 @@ function utils.deepCopy(original) return copy end +--- Gets the length of a table +--- @param table table The table to get the length of +--- @return number The length of the table function utils.lengthOfTable(table) local count = 0 for _, val in pairs(table) do @@ -237,12 +283,20 @@ function utils.lengthOfTable(table) end return count end + +--- Gets a hash from a base64 URL encoded string +--- @param str string The base64 URL encoded string +--- @return table The hash function utils.getHashFromBase64URL(str) local decodedHash = base64.decode(str, base64.URL_DECODER) local hashStream = crypto.utils.stream.fromString(decodedHash) return crypto.digest.sha2_256(hashStream).asBytes() end +--- Splits a string by a delimiter +--- @param input string The string to split +--- @param delimiter string The delimiter to split by +--- @return table The split string function utils.splitString(input, delimiter) delimiter = delimiter or "," local result = {} @@ -252,10 +306,17 @@ function utils.splitString(input, delimiter) return result end +--- Trims a string +--- @param input string The string to trim +--- @return string The trimmed string function utils.trimString(input) return input:match("^%s*(.-)%s*$") end +--- Splits a string by a delimiter and trims each token +--- @param input string The string to split +--- @param delimiter string The delimiter to split by +--- @return table The split and trimmed string function utils.splitAndTrimString(input, delimiter) local tokens = {} for _, token in ipairs(utils.splitString(input, delimiter)) do @@ -267,11 +328,13 @@ function utils.splitAndTrimString(input, delimiter) return tokens end +--- Checks if a timestamp is an integer and converts it to milliseconds if it is in seconds +--- @param timestamp number The timestamp to check and convert +--- @return number The timestamp in milliseconds function utils.checkAndConvertTimestamptoMs(timestamp) -- Check if the timestamp is an integer - if type(timestamp) ~= "number" or timestamp % 1 ~= 0 then - return error("Timestamp must be an integer") - end + assert(type(timestamp) == "number", "Timestamp must be a number") + assert(utils.isInteger(timestamp), "Timestamp must be an integer") -- Define the plausible range for Unix timestamps in seconds local min_timestamp = 0 @@ -290,7 +353,7 @@ function utils.checkAndConvertTimestamptoMs(timestamp) return timestamp end - return error("Timestamp is out of range") + error("Timestamp is out of range") end function utils.reduce(tbl, fn, init)