From 9a1fc1ddcac8b6e96b7ccca88cc74d763618cb6f Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Mon, 11 Nov 2024 13:08:53 -0600 Subject: [PATCH 1/8] fix(records): return records as sorted entries --- CHANGELOG.md | 1 + src/common/records.lua | 16 +++++++++++- src/common/utils.lua | 10 ++++++++ test/records.test.mjs | 58 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 77 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a1783..5140adf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Refactored handlers to use a util that codifies responses on calls. - Added documentation with luadoc types for improved linting. +- Records are now returned as an alphabetically sorted array of [undername, {transactionId, ttlSeconds}] with the '@' record being the first. ### Fixed diff --git a/src/common/records.lua b/src/common/records.lua index 9237e7c..4cc9465 100644 --- a/src/common/records.lua +++ b/src/common/records.lua @@ -53,7 +53,21 @@ end --- Get all records from the ANT ---@return string The encoded JSON representation of all records. function records.getRecords() - return json.encode(Records) + local antRecords = utils.deepCopy(Records) + assert(antRecords, "Failed to copy Records") + local recordEntries = utils.entries(antRecords) + -- sort the records alphabetically, ensuring "@" record is first + table.sort(recordEntries, function(a, b) + if a[1] == "@" then + return true + end + if b[1] == "@" then + return false + end + return a[1] < b[1] + end) + + return json.encode(recordEntries) end return records diff --git a/src/common/utils.lua b/src/common/utils.lua index 044e6dd..ac9834d 100644 --- a/src/common/utils.lua +++ b/src/common/utils.lua @@ -189,6 +189,16 @@ utils.values = function(t) return values end +---@param t table +---@return table +utils.entries = function(t) + local entries = {} + for _, key in ipairs(utils.keys(t)) do + table.insert(entries, { key, t[key] }) + end + return entries +end + function utils.hasMatchingTag(tag, value) return Handlers.utils.hasMatchingTag(tag, value) end diff --git a/test/records.test.mjs b/test/records.test.mjs index 3298730..dc9a789 100644 --- a/test/records.test.mjs +++ b/test/records.test.mjs @@ -4,6 +4,7 @@ import assert from 'node:assert'; import { AO_LOADER_HANDLER_ENV, DEFAULT_HANDLE_OPTIONS, + STUB_ADDRESS, } from '../tools/constants.mjs'; describe('aos Records', async () => { @@ -21,14 +22,53 @@ describe('aos Records', async () => { ); } + async function setRecord( + { name, ttl = 3600, transactionId = STUB_ADDRESS }, + mem, + ) { + return handle( + { + Tags: [ + { name: 'Action', value: 'Set-Record' }, + { name: 'Sub-Domain', value: name }, + { name: 'TTL-Seconds', value: ttl }, + { name: 'Transaction-Id', value: transactionId }, + ], + }, + mem, + ); + } + + async function getRecords(mem) { + const res = await handle( + { + Tags: [{ name: 'Action', value: 'Records' }], + }, + mem, + ); + + return JSON.parse(res.Messages[0].Data); + } + it('should get the records of the ant', async () => { - const result = await handle({ - Tags: [{ name: 'Action', value: 'Records' }], - }); + const setRecordRes = await setRecord({ name: 'test-1' }); + const setRecordRes2 = await setRecord( + { name: 'test-2' }, + setRecordRes.Memory, + ); + const setRecordRes3 = await setRecord( + { name: 'test-3' }, + setRecordRes2.Memory, + ); - const records = JSON.parse(result.Messages[0].Data); + const records = await getRecords(setRecordRes3.Memory); assert(records); - assert(records['@']); + const recordsMap = Object.fromEntries(records); + assert(recordsMap['@']); + // assert record order + const undernames = Object.keys(recordsMap); + assert(undernames[0] == '@'); + assert.strictEqual(undernames.at(-1), 'test-3'); }); it('should get a singular record of the ant', async () => { @@ -62,7 +102,9 @@ describe('aos Records', async () => { setRecordResult.Memory, ); - const record = JSON.parse(recordsResult.Messages[0].Data)['@']; + const record = Object.fromEntries( + JSON.parse(recordsResult.Messages[0].Data), + )['@']; assert(record.transactionId === ''.padEnd(43, '3')); assert(record.ttlSeconds === 3600); }); @@ -115,7 +157,9 @@ describe('aos Records', async () => { setRecordResult.Memory, ); - const record = JSON.parse(recordsResult.Messages[0].Data)['timmy']; + const record = Object.fromEntries( + JSON.parse(recordsResult.Messages[0].Data), + )['timmy']; assert(record.transactionId === ''.padEnd(43, '3')); assert(record.ttlSeconds === 3600); }); From 30824b5c9392d826e562c4d833fb7ebed4fe01a7 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Mon, 11 Nov 2024 13:20:13 -0600 Subject: [PATCH 2/8] fix(records): do not encode in getter, allow handler to handle encoding of the response --- src/common/records.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/records.lua b/src/common/records.lua index 4cc9465..a6c732e 100644 --- a/src/common/records.lua +++ b/src/common/records.lua @@ -51,7 +51,7 @@ function records.getRecord(name) end --- Get all records from the ANT ----@return string The encoded JSON representation of all records. +---@return table
The sorted records of the ANT function records.getRecords() local antRecords = utils.deepCopy(Records) assert(antRecords, "Failed to copy Records") @@ -67,7 +67,7 @@ function records.getRecords() return a[1] < b[1] end) - return json.encode(recordEntries) + return recordEntries end return records From e1f8b755a516bb24c8d746247fffb9355f182b87 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Tue, 12 Nov 2024 09:11:13 -0600 Subject: [PATCH 3/8] fix(records): return flat map of records with name keys --- CHANGELOG.md | 2 +- src/common/records.lua | 15 ++++++++++----- test/records.test.mjs | 19 +++++++------------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5140adf..d9ae8cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Refactored handlers to use a util that codifies responses on calls. - Added documentation with luadoc types for improved linting. -- Records are now returned as an alphabetically sorted array of [undername, {transactionId, ttlSeconds}] with the '@' record being the first. +- Records are now returned as an alphabetically sorted array of [{name, transactionId, ttlSeconds}] with the '@' record being the first. ### Fixed diff --git a/src/common/records.lua b/src/common/records.lua index a6c732e..9575fe9 100644 --- a/src/common/records.lua +++ b/src/common/records.lua @@ -55,16 +55,21 @@ end function records.getRecords() local antRecords = utils.deepCopy(Records) assert(antRecords, "Failed to copy Records") - local recordEntries = utils.entries(antRecords) - -- sort the records alphabetically, ensuring "@" record is first + local recordEntries = {} + + for undername, record in pairs(antRecords) do + local entry = record + entry.name = undername + table.insert(recordEntries, entry) + end table.sort(recordEntries, function(a, b) - if a[1] == "@" then + if a.name == "@" then return true end - if b[1] == "@" then + if b.name == "@" then return false end - return a[1] < b[1] + return a.name < b.name end) return recordEntries diff --git a/test/records.test.mjs b/test/records.test.mjs index dc9a789..d34cb39 100644 --- a/test/records.test.mjs +++ b/test/records.test.mjs @@ -63,12 +63,9 @@ describe('aos Records', async () => { const records = await getRecords(setRecordRes3.Memory); assert(records); - const recordsMap = Object.fromEntries(records); - assert(recordsMap['@']); - // assert record order - const undernames = Object.keys(recordsMap); - assert(undernames[0] == '@'); - assert.strictEqual(undernames.at(-1), 'test-3'); + + assert.strictEqual(records[0].name, '@'); + assert.strictEqual(records.at(-1).name, 'test-3'); }); it('should get a singular record of the ant', async () => { @@ -102,9 +99,8 @@ describe('aos Records', async () => { setRecordResult.Memory, ); - const record = Object.fromEntries( - JSON.parse(recordsResult.Messages[0].Data), - )['@']; + const records = JSON.parse(recordsResult.Messages[0].Data); + const record = records[0]; assert(record.transactionId === ''.padEnd(43, '3')); assert(record.ttlSeconds === 3600); }); @@ -157,9 +153,8 @@ describe('aos Records', async () => { setRecordResult.Memory, ); - const record = Object.fromEntries( - JSON.parse(recordsResult.Messages[0].Data), - )['timmy']; + const records = JSON.parse(recordsResult.Messages[0].Data); + const record = records.find((r) => r.name == 'timmy'); assert(record.transactionId === ''.padEnd(43, '3')); assert(record.ttlSeconds === 3600); }); From ff0a5345208ccb848464d467360cf03ff5a1bf8e Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Tue, 12 Nov 2024 09:22:34 -0600 Subject: [PATCH 4/8] fix(readme): update readme --- README.md | 59 ++++++++++++++++++++++++++++++++---------- src/common/records.lua | 9 ++++++- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 7173e0a..366bdde 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,10 @@ This repository provides two flavours of ANT process module, AOS and a custom mo - [Handler Methods](#handler-methods) - [Read Methods](#read-methods) - [`Info`](#info) - - [`Get-Records`](#get-records) - - [`Get-Record`](#get-record) - - [`Get-Controllers`](#get-controllers) + - [`State`](#state) + - [`Records`](#records) + - [`Record`](#record) + - [`Controllers`](#controllers) - [`Balance`](#balance) - [`Balances`](#balances) - [Write methods](#write-methods) @@ -28,6 +29,7 @@ This repository provides two flavours of ANT process module, AOS and a custom mo - [`Set-Name`](#set-name) - [`Set-Ticker`](#set-ticker) - [`Set-Description`](#set-description) + - [`Set-Logo`](#set-logo) - [`Set-Keywords`](#set-keywords) - [`Set-Controller`](#set-controller) - [`Remove-Controller`](#remove-controller) @@ -122,30 +124,52 @@ Retrieves the Name, Ticker, Total supply, Logo, Denomination, and Owner of the A | -------- | ------ | ------- | -------- | --------------------------------- | | Action | string | "Info" | true | Action tag for triggering handler | -#### `Get-Records` +#### `State` + +Retrieves the entire state of the ANT, which includes: + +- Records +- Controllers +- Balances +- Owner +- Name +- Ticker +- Logo +- Description +- Keywords +- Denomination +- TotalSupply +- Initialized +- Source-Code-TX-ID + +| Tag Name | Type | Pattern | Required | Description | +| -------- | ------ | ------- | -------- | --------------------------------- | +| Action | string | "State" | true | Action tag for triggering handler | + +#### `Records` Retrieves all the records configured on the ANT -| Tag Name | Type | Pattern | Required | Description | -| -------- | ------ | ------------- | -------- | --------------------------------- | -| Action | string | "Get-Records" | true | Action tag for triggering handler | +| Tag Name | Type | Pattern | Required | Description | +| -------- | ------ | --------- | -------- | --------------------------------- | +| Action | string | "Records" | true | Action tag for triggering handler | -#### `Get-Record` +#### `Record` Retrieves and individual record by name. | Tag Name | Type | Pattern | Required | Description | | ---------- | ------ | ------------------------- | -------- | --------------------------------- | -| Action | string | "Get-Record" | true | Action tag for triggering handler | +| Action | string | "Record" | true | Action tag for triggering handler | | Sub-Domain | string | "^(?:[a-zA-Z0-9_-]+\|@)$" | true | Subdomain you which to read | -#### `Get-Controllers` +#### `Controllers` Retrieves all the controllers on the ANT. -| Tag Name | Type | Pattern | Required | Description | -| -------- | ------ | ----------------- | -------- | --------------------------------- | -| Action | string | "Get-Controllers" | true | Action tag for triggering handler | +| Tag Name | Type | Pattern | Required | Description | +| -------- | ------ | ------------- | -------- | --------------------------------- | +| Action | string | "Controllers" | true | Action tag for triggering handler | #### `Balance` @@ -213,6 +237,15 @@ Sets the description for the ANT. | Action | string | "Set-Description" | true | Action tag for triggering handler | | Description | string | Max 512 characters | true | New description for ANT. | +#### `Set-Logo` + +Sets the logo for the ANT. + +| Tag Name | Type | Pattern | Required | Description | +| -------- | ------ | --------------------- | -------- | --------------------------------- | +| Action | string | "Set-Logo" | true | Action tag for triggering handler | +| Logo | string | "^[a-zA-Z0-9_-]{43}$" | true | ID of new logo for ANT. | + #### `Set-Keywords` Sets the keywords for the ANT. diff --git a/src/common/records.lua b/src/common/records.lua index 9575fe9..6d0cc2c 100644 --- a/src/common/records.lua +++ b/src/common/records.lua @@ -51,10 +51,17 @@ function records.getRecord(name) end --- Get all records from the ANT ----@return table
The sorted records of the ANT +---@alias RecordEntry { +--- name: string, +--- transactionId: string, +--- ttlSeconds: integer, +---} +---@return table The sorted records of the ANT function records.getRecords() local antRecords = utils.deepCopy(Records) assert(antRecords, "Failed to copy Records") + + ---@type table local recordEntries = {} for undername, record in pairs(antRecords) do From f0d6e01522f21fa532b1a2722b274d320af50cda Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Tue, 12 Nov 2024 09:35:32 -0600 Subject: [PATCH 5/8] fix(readme): update existing handler explanations --- README.md | 15 ++++++++++++--- src/common/main.lua | 6 +++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 366bdde..eb5a209 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ This repository provides two flavours of ANT process module, AOS and a custom mo - [Handler Methods](#handler-methods) - [Read Methods](#read-methods) - [`Info`](#info) + - [`Total-Supply`](#total-supply) - [`State`](#state) - [`Records`](#records) - [`Record`](#record) @@ -31,7 +32,7 @@ This repository provides two flavours of ANT process module, AOS and a custom mo - [`Set-Description`](#set-description) - [`Set-Logo`](#set-logo) - [`Set-Keywords`](#set-keywords) - - [`Set-Controller`](#set-controller) + - [`Add-Controller`](#add-controller) - [`Remove-Controller`](#remove-controller) - [`Remove-Record`](#remove-record) - [`Release-Name`](#release-name) @@ -124,6 +125,14 @@ Retrieves the Name, Ticker, Total supply, Logo, Denomination, and Owner of the A | -------- | ------ | ------- | -------- | --------------------------------- | | Action | string | "Info" | true | Action tag for triggering handler | +#### `Total-Supply` + +Retrieves total supply of the ANT. + +| Tag Name | Type | Pattern | Required | Description | +| -------- | ------ | -------------- | -------- | --------------------------------- | +| Action | string | "Total-Supply" | true | Action tag for triggering handler | + #### `State` Retrieves the entire state of the ANT, which includes: @@ -255,13 +264,13 @@ Sets the keywords for the ANT. | Action | string | "Set-Keywords" | true | Action tag for triggering handler | | Keywords | table | "^[%w-_#@]+$", max 32 characters, max 16 keywords, min 1 keyword | true | New keywords for ANT. | -#### `Set-Controller` +#### `Add-Controller` Adds a new controller to the ANT. | Tag Name | Type | Pattern | Required | Description | | ---------- | ------ | --------------------- | -------- | --------------------------------- | -| Action | string | "Set-Controller" | true | Action tag for triggering handler | +| Action | string | "Add-Controller" | true | Action tag for triggering handler | | Controller | string | "^[a-zA-Z0-9_-]{43}$" | true | Address of the new controller. | #### `Remove-Controller` diff --git a/src/common/main.lua b/src/common/main.lua index 6ec0b60..dc6ccb0 100644 --- a/src/common/main.lua +++ b/src/common/main.lua @@ -85,10 +85,10 @@ function ant.init() Balance = "Balance", Transfer = "Transfer", TotalSupply = "Total-Supply", - CreditNotice = "Credit-Notice", -- not implemented - Mint = "Mint", - Burn = "Burn", + -- CreditNotice = "Credit-Notice", + -- Mint = "Mint", + -- Burn = "Burn", } createActionHandler(TokenSpecActionMap.Transfer, function(msg) From 5a080c72175a6e604b290ae89e5bd53fc3cbbee1 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Tue, 12 Nov 2024 11:41:31 -0600 Subject: [PATCH 6/8] fix(tests): update unit tests --- .busted | 11 +++++------ .gitignore | 4 ++-- .luacov | 5 +++++ spec/ant_spec.lua | 9 +++++++++ 4 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 .luacov diff --git a/.busted b/.busted index c838de1..9ce6381 100644 --- a/.busted +++ b/.busted @@ -1,14 +1,13 @@ print("Loading .busted configuration...") + return { - _all = { + default = { + root = "src", + pattern = "**/*_spec.lua$", + helper = "spec/setup.lua", verbose = true, coverage = true, output = "utfTerminal", jobs = 4, - }, - default = { - ROOT = {"ant"}, - pattern = "**/*_spec.lua$", - helper = "spec/setup.lua", } } diff --git a/.gitignore b/.gitignore index 3ae07f1..e0ad6f4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,9 +10,9 @@ node_modules wallet.json .DS_Store luacov-html -.luacov *.gz luacov.stats.out process.wasm dist -publish-output.json \ No newline at end of file +publish-output.json +luarocks-3.9.1 \ No newline at end of file diff --git a/.luacov b/.luacov new file mode 100644 index 0000000..25206f9 --- /dev/null +++ b/.luacov @@ -0,0 +1,5 @@ +statsfile = 'coverage/luacov.stats.out'; +reportfile = 'coverage/luacov.report.out'; +deleteStatsFile = false; +include = { "src/" } +exclude = { "crypto", "json", "base64", "ao" } diff --git a/spec/ant_spec.lua b/spec/ant_spec.lua index d2fb754..c6b1872 100644 --- a/spec/ant_spec.lua +++ b/spec/ant_spec.lua @@ -82,6 +82,15 @@ describe("Arweave Name Token", function() assert.are.same(_G.Records["@"].ttlSeconds, 900) end) + it("gets all records", function() + records.setRecord("zed", string.rep("1", 43), 3600) + records.setRecord("@", string.rep("1", 43), 3600) + local recordEntries = records.getRecords() + + assert.are.same(recordEntries[1].name, "@") + assert.are.same(recordEntries[#recordEntries].name, "zed") + end) + it("removes a record", function() local name = "@" records.removeRecord(name) -- happy path From 25dc11aaf8ee968c06bf8a7e2aa65046c2e0283d Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Tue, 12 Nov 2024 11:42:38 -0600 Subject: [PATCH 7/8] fix(utils): remove unused util --- src/common/utils.lua | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/common/utils.lua b/src/common/utils.lua index ac9834d..044e6dd 100644 --- a/src/common/utils.lua +++ b/src/common/utils.lua @@ -189,16 +189,6 @@ utils.values = function(t) return values end ----@param t table ----@return table
-utils.entries = function(t) - local entries = {} - for _, key in ipairs(utils.keys(t)) do - table.insert(entries, { key, t[key] }) - end - return entries -end - function utils.hasMatchingTag(tag, value) return Handlers.utils.hasMatchingTag(tag, value) end From 16220a98103d5b7c913d7c304baeebfa4b66ea9d Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Tue, 12 Nov 2024 14:29:22 -0600 Subject: [PATCH 8/8] fix(test): set the state of the target test records manually --- spec/ant_spec.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/ant_spec.lua b/spec/ant_spec.lua index c6b1872..f510fc1 100644 --- a/spec/ant_spec.lua +++ b/spec/ant_spec.lua @@ -83,8 +83,14 @@ describe("Arweave Name Token", function() end) it("gets all records", function() - records.setRecord("zed", string.rep("1", 43), 3600) - records.setRecord("@", string.rep("1", 43), 3600) + _G.Records["zed"] = { + transactionId = string.rep("1", 43), + ttlSeconds = 3600, + } + _G.Records["@"] = { + transactionId = string.rep("1", 43), + ttlSeconds = 3600, + } local recordEntries = records.getRecords() assert.are.same(recordEntries[1].name, "@")