diff --git a/src/common/main.lua b/src/common/main.lua index 38a30bd..33765a2 100644 --- a/src/common/main.lua +++ b/src/common/main.lua @@ -42,6 +42,8 @@ function ant.init() Records = "Records", State = "State", Evolve = "Evolve", + -- IO Network Contract Handlers + ReleaseName = "Release-Name", } local TokenSpecActionMap = { @@ -450,6 +452,37 @@ function ant.init() SourceCodeTxId = srcCodeTxId end ) + + -- IO Network Contract Handlers + Handlers.add(camel(ActionMap.ReleaseName), utils.hasMatchingTag("Action", ActionMap.ReleaseName), function(msg) + local assertHasPermission, permissionErr = pcall(utils.validateOwner, msg.From) + if assertHasPermission == false then + return ao.send({ + Target = msg.From, + Action = "Invalid-Release-Name-Notice", + Data = permissionErr, + Error = "Release-Name-Error", + }) + end + + local name = string.lower(msg.Tags["Name"]) + local ioProcess = msg.Tags["IO-Process-Id"] + + -- send the release message to the provided IO Process Id + ao.send({ + Target = ioProcess, + Action = "Release-Name-Notice", + Initiator = msg.From, + Name = name, + }) + + ao.send({ + Target = msg.From, + Action = "Release-Name-Notice", + Initiator = msg.From, + Name = name, + }) + end) end return ant diff --git a/test/info.test.mjs b/test/info.test.mjs index 678b8a8..1942912 100644 --- a/test/info.test.mjs +++ b/test/info.test.mjs @@ -54,6 +54,7 @@ describe('aos Info', async () => { 'setTicker', 'initializeState', 'state', + 'releaseName', ]); }); diff --git a/test/io.test.mjs b/test/io.test.mjs new file mode 100644 index 0000000..6d0ed1f --- /dev/null +++ b/test/io.test.mjs @@ -0,0 +1,93 @@ +import { createAntAosLoader } from './utils.mjs'; +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { + AO_LOADER_HANDLER_ENV, + DEFAULT_HANDLE_OPTIONS, + STUB_ADDRESS, +} from '../tools/constants.mjs'; + +describe('IO Network Updates', async () => { + const { handle: originalHandle, memory: startMemory } = + await createAntAosLoader(); + + async function handle(options = {}, mem = startMemory) { + return originalHandle( + mem, + { + ...DEFAULT_HANDLE_OPTIONS, + ...options, + }, + AO_LOADER_HANDLER_ENV, + ); + } + + it('should send update to io network when a name is released', async () => { + const result = await handle({ + Tags: [ + { name: 'Action', value: 'Release-Name' }, + { name: 'IO-Process-Id', value: 'io-process-id-'.padEnd(43, '1') }, + { name: 'Name', value: 'name' }, + ], + }); + + // two messages should be sent - one to the io process and one to the sender + assert(result.Messages?.length === 2, 'Expected two messages'); + + const message = result.Messages[0]?.Tags?.find( + (tag) => tag.name === 'Action' && tag.value === 'Release-Name-Notice', + ); + assert(message, 'Release-Name-Notice message not found'); + + // ensure a message Target is the IO process id provided and the other is to the sender and both contain the initiator, name and process id + assert( + result.Messages.some( + (msg) => + msg.Target === 'io-process-id-'.padEnd(43, '1') && + msg.Tags.find( + (tag) => tag.name === 'Initiator' && tag.value === STUB_ADDRESS, + ) && + msg.Tags.find((tag) => tag.name === 'Name' && tag.value === 'name') && + msg.Tags.find( + (tag) => + tag.name === 'Action' && tag.value === 'Release-Name-Notice', + ), + ), + ); + + assert( + result.Messages.some( + (msg) => + msg.Target === STUB_ADDRESS && + msg.Tags.find( + (tag) => tag.name === 'Initiator' && tag.value === STUB_ADDRESS, + ) && + msg.Tags.find((tag) => tag.name === 'Name' && tag.value === 'name') && + msg.Tags.find( + (tag) => + tag.name === 'Action' && tag.value === 'Release-Name-Notice', + ), + ), + ); + }); + + it('should send a release-name-error-notice if the sender is not the owner', async () => { + const result = await handle({ + Tags: [ + { name: 'Action', value: 'Release-Name' }, + { name: 'IO-Process-Id', value: 'io-process-id' }, + { name: 'Name', value: 'name' }, + ], + From: 'not-owner', + Owner: 'not-owner', + }); + + // assert no other messages + assert(result.Messages?.length === 1, 'Expected only one message'); + + const error = result.Messages[0]?.Tags.find( + (tag) => tag.name === 'Error' && tag.value === 'Release-Name-Error', + ); + assert(error, 'Release-Name-Error message not found'); + }); +}); diff --git a/tools/constants.mjs b/tools/constants.mjs index 3388084..74eb6d3 100644 --- a/tools/constants.mjs +++ b/tools/constants.mjs @@ -5,11 +5,12 @@ import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const STUB_ADDRESS = ''.padEnd(43, '1'); +const STUB_PROCESS_ID = 'process-id-'.padEnd(43, '1'); +const STUB_ADDRESS = 'arweave-address-'.padEnd(43, '1'); /* ao READ-ONLY Env Variables */ const AO_LOADER_HANDLER_ENV = { Process: { - Id: ''.padEnd(43, '1'), + Id: STUB_PROCESS_ID, Owner: STUB_ADDRESS, Tags: [ { name: 'Authority', value: 'XXXXXX' }, @@ -84,6 +85,7 @@ export { AO_LOADER_OPTIONS, AO_LOADER_HANDLER_ENV, STUB_ADDRESS, + STUB_PROCESS_ID, DEFAULT_HANDLE_OPTIONS, ANT_EVAL_OPTIONS, };