Skip to content

Commit

Permalink
Merge pull request #25 from ar-io/PE-6941-add-description-and-keywords
Browse files Browse the repository at this point in the history
PE-6941 add description and keywords
  • Loading branch information
vilenarios authored Oct 22, 2024
2 parents a6b3506 + 81d21bc commit d6d597e
Show file tree
Hide file tree
Showing 13 changed files with 310 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on: [push, workflow_dispatch]

jobs:
unit:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
name: Check out repository code
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,24 @@ Sets the ticker symbol for the ANT.
| Action | string | "Set-Ticker" | true | Action tag for triggering handler |
| Ticker | string | N/A | true | New ticker symbol for ANT. |

#### `Set-Description`

Sets the description for the ANT.

| Tag Name | Type | Pattern | Required | Description |
| ----------- | ------ | ------------------ | -------- | --------------------------------- |
| Action | string | "Set-Description" | true | Action tag for triggering handler |
| Description | string | Max 512 characters | true | New description for ANT. |

#### `Set-Keywords`

Sets the keywords for the ANT.

| Tag Name | Type | Pattern | Required | Description |
| -------- | ------ | ---------------------------------------------------------------- | -------- | --------------------------------- |
| 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`

Adds a new controller to the ANT.
Expand Down
71 changes: 71 additions & 0 deletions spec/ant_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ local fake_address = "1111111111111111111111111111111111111111111"
local originalState = {
name = "Arweave Name Token",
ticker = "ANT",
description = "ANT's description",
keywords = { "KEYWORD-1", "KEYWORD-2", "KEYWORD-3" },
controllers = { fake_address },
records = { ["@"] = { transactionId = fake_address, ttlSeconds = 900 } },
balances = { [fake_address] = 1 },
Expand All @@ -22,6 +24,8 @@ describe("Arweave Name Token", function()
_G.Controllers = { fake_address }
_G.Name = "Arweave Name Token"
_G.Ticker = "ANT"
_G.Description = "ANT's description"
_G.Keywords = { "KEYWORD-1", "KEYWORD-2", "KEYWORD-3" }
_G.Denomination = 1
end)

Expand All @@ -33,6 +37,8 @@ describe("Arweave Name Token", function()
assert.are.same(_G.Controllers, originalState.controllers)
assert.are.same(_G.Name, originalState.name)
assert.are.same(_G.Ticker, originalState.ticker)
assert.are.same(_G.Description, originalState.description)
assert.are.same(_G.Keywords, originalState.keywords)
end)

it("Transfers tokens between accounts", function()
Expand Down Expand Up @@ -96,4 +102,69 @@ describe("Arweave Name Token", function()

assert.are.same(_G.Ticker, newTicker)
end)

it("sets the description", function()
local newDescription = "NEW DESCRIPTION"
balances.setDescription(newDescription) -- happy path

assert.are.same(_G.Description, newDescription)
end)

it("sets the keywords", function()
local newKeywords = { "NEW-KEYWORD-1", "NEW-KEYWORD-2", "NEW-KEYWORD-3" }
balances.setKeywords(newKeywords) -- setKeywords now handles JSON string

assert.are.same(_G.Keywords, newKeywords)
end)

-- Test when too many keywords are provided
it("throws an error if keywords exceed 16 elements", function()
local tooManyKeywords = {}
for i = 1, 17 do -- 17 keywords, exceeds limit
table.insert(tooManyKeywords, "keyword" .. i)
end
assert.has_error(function()
balances.setKeywords(tooManyKeywords)
end, "There must not be more than 16 keywords")
end)

-- Test when any keyword is too long
it("throws an error if any keyword is too long", function()
local keywordsWithLongEntry = { "valid", string.rep("a", 33) } -- Second keyword is 33 characters long
assert.has_error(function()
balances.setKeywords(keywordsWithLongEntry)
end, "Each keyword must not be longer than 32 characters")
end)

-- Test when any keyword contains spaces
it("throws an error if any keyword contains spaces", function()
local keywordsWithSpace = { "valid", "invalid keyword" } -- Contains a space
assert.has_error(function()
balances.setKeywords(keywordsWithSpace)
end, "Keywords must not contain spaces")
end)

-- Test when keywords contain invalid characters
it("throws an error if keywords contain invalid characters", function()
local keywordsWithSpecialChars = { "valid", "inva!lid" } -- Contains special character '!'
assert.has_error(function()
balances.setKeywords(keywordsWithSpecialChars)
end, "Keywords must only contain alphanumeric characters, dashes, underscores, #, or @")
end)

-- Test when any keyword is duplicated
it("throws an error if any keyword is duplicated", function()
local keywordsWithDuplicates = { "keyword", "keyword" } -- Duplicate keyword
assert.has_error(function()
balances.setKeywords(keywordsWithDuplicates)
end, "Duplicate keyword detected: keyword")
end)

-- Test when the keywords array is not actually an array
it("throws an error if the keywords array is not actually an array", function()
local notAnArray = "not-an-array"
assert.has_error(function()
balances.setKeywords(notAnArray)
end, "Keywords must be an array")
end)
end)
14 changes: 14 additions & 0 deletions src/common/balances.lua
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,18 @@ function balances.setTicker(ticker)
return json.encode({ ticker = Ticker })
end

function balances.setDescription(description)
assert(type(description) == "string", "Description must be a string")
assert(#description <= 512, "Description must not be longer than 512 characters")
Description = description
return json.encode({ description = Description })
end

function balances.setKeywords(keywords)
utils.validateKeywords(keywords)

Keywords = keywords
return json.encode({ keywords = Keywords })
end

return balances
10 changes: 10 additions & 0 deletions src/common/initialize.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ function initialize.initializeANTState(state)
local records = encoded.records
local name = encoded.name
local ticker = encoded.ticker
local description = encoded.description
local keywords = encoded.keywords
local owner = encoded.owner
assert(type(name) == "string", "name must be a string")
assert(type(ticker) == "string", "ticker must be a string")
assert(type(description) == "string", "description must be a string")
assert(#description <= 512, "Description must not be longer than 512 characters")
assert(type(balances) == "table", "balances must be a table")
for k, v in pairs(balances) do
balances[k] = tonumber(v)
Expand All @@ -26,8 +30,12 @@ function initialize.initializeANTState(state)
utils.validateTTLSeconds(v.ttlSeconds)
end

utils.validateKeywords(keywords)

Name = name
Ticker = ticker
Description = description
Keywords = keywords
Balances = balances
Controllers = controllers
Records = records
Expand All @@ -37,6 +45,8 @@ function initialize.initializeANTState(state)
return json.encode({
name = Name,
ticker = Ticker,
description = Description,
keywords = Keywords,
balances = Balances,
controllers = Controllers,
records = Records,
Expand Down
75 changes: 75 additions & 0 deletions src/common/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ function ant.init()
Name = Name or "Arweave Name Token"
Ticker = Ticker or "ANT"
Logo = Logo or "Sie_26dvgyok0PZD_-iQAFOhOd5YxDTkczOLoqTTL_A"
Description = Description or "A brief description of this ANT."
Keywords = Keywords or {}
Denomination = Denomination or 0
TotalSupply = TotalSupply or 1
Initialized = Initialized or false
Expand All @@ -34,6 +36,8 @@ function ant.init()
RemoveRecord = "Remove-Record",
SetName = "Set-Name",
SetTicker = "Set-Ticker",
SetDescription = "Set-Description",
SetKeywords = "Set-Keywords",
--- initialization method for bootstrapping the contract from other platforms ---
InitializeState = "Initialize-State",
-- read
Expand Down Expand Up @@ -160,6 +164,8 @@ function ant.init()
Ticker = Ticker,
["Total-Supply"] = tostring(TotalSupply),
Logo = Logo,
Description = Description,
Keywords = Keywords,
Denomination = tostring(Denomination),
Owner = Owner,
Handlers = utils.getHandlerNames(Handlers),
Expand Down Expand Up @@ -392,6 +398,75 @@ function ant.init()
ao.send({ Target = msg.From, Action = "Set-Ticker-Notice", Data = tickerRes })
end)

Handlers.add(
camel(ActionMap.SetDescription),
utils.hasMatchingTag("Action", ActionMap.SetDescription),
function(msg)
local assertHasPermission, permissionErr = pcall(utils.assertHasPermission, msg.From)
if assertHasPermission == false then
return ao.send({
Target = msg.From,
Action = "Invalid-Set-Description-Notice",
Data = permissionErr,
Error = "Set-Description-Error",
["Message-Id"] = msg.Id,
})
end
local descriptionStatus, descriptionRes = pcall(balances.setDescription, msg.Tags.Description)
if not descriptionStatus then
ao.send({
Target = msg.From,
Action = "Invalid-Set-Description-Notice",
Data = descriptionRes,
Error = "Set-Description-Error",
["Message-Id"] = msg.Id,
})
return
end

ao.send({ Target = msg.From, Action = "Set-Description-Notice", Data = descriptionRes })
end
)

Handlers.add(camel(ActionMap.SetKeywords), utils.hasMatchingTag("Action", ActionMap.SetKeywords), function(msg)
local assertHasPermission, permissionErr = pcall(utils.assertHasPermission, msg.From)
if assertHasPermission == false then
return ao.send({
Target = msg.From,
Action = "Invalid-Set-Keywords-Notice",
Data = permissionErr,
Error = "Set-Keywords-Error",
["Message-Id"] = msg.Id,
})
end

-- Decode JSON from the tag
local success, keywords = pcall(json.decode, msg.Tags.Keywords)
if not success or type(keywords) ~= "table" then
return ao.send({
Target = msg.From,
Action = "Invalid-Set-Keywords-Notice",
Data = "Invalid JSON format for keywords",
Error = "Set-Keywords-Error",
["Message-Id"] = msg.Id,
})
end

local keywordsStatus, keywordsRes = pcall(balances.setKeywords, keywords)
if not keywordsStatus then
ao.send({
Target = msg.From,
Action = "Invalid-Set-Keywords-Notice",
Data = keywordsRes,
Error = "Set-Keywords-Error",
["Message-Id"] = msg.Id,
})
return
end

ao.send({ Target = msg.From, Action = "Set-Keywords-Notice", Data = keywordsRes })
end)

Handlers.add(
camel(ActionMap.InitializeState),
utils.hasMatchingTag("Action", ActionMap.InitializeState),
Expand Down
22 changes: 22 additions & 0 deletions src/common/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ function utils.notices.notifyState(msg, target)
Name = Name,
Ticker = Ticker,
Logo = Logo,
Description = Description,
Keywords = Keywords,
Denomination = Denomination,
TotalSupply = TotalSupply,
Initialized = Initialized,
Expand All @@ -340,4 +342,24 @@ function utils.getHandlerNames(handlers)
return names
end

function utils.validateKeywords(keywords)
assert(type(keywords) == "table", "Keywords must be an array")
assert(#keywords <= 16, "There must not be more than 16 keywords")

local seenKeywords = {} -- Table to track seen keywords

for _, keyword in ipairs(keywords) do
assert(type(keyword) == "string", "Each keyword must be a string")
assert(#keyword <= 32, "Each keyword must not be longer than 32 characters")
assert(not keyword:find("%s"), "Keywords must not contain spaces")
assert(
keyword:match("^[%w-_#@]+$"),
"Keywords must only contain alphanumeric characters, dashes, underscores, #, or @"
)
-- Check for duplicates
assert(not seenKeywords[keyword], "Duplicate keyword detected: " .. keyword)
seenKeywords[keyword] = true
end
end

return utils
Loading

0 comments on commit d6d597e

Please sign in to comment.