From 70736436b616c7e1277e9ee769cfb30b6a31b75f Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Fri, 8 Nov 2024 12:31:17 -0600 Subject: [PATCH 01/28] fix(readme): update readme with new APIs --- README.md | 99 +++++++++++++++++----------- package.json | 4 ++ yarn.lock | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 246 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 2eaf278..7173e0a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,47 @@ This repository contains the source code used for Arweave Name Tokens used to re This repository provides two flavours of ANT process module, AOS and a custom module. + + +- [Setup](#setup) + - [Install](#install) + - [Testing](#testing) + - [Building the AOS code](#building-the-aos-code) + - [Build](#build) + - [Publish](#publish) + - [Load](#load) + - [Spawn](#spawn) +- [Handler Methods](#handler-methods) + - [Read Methods](#read-methods) + - [`Info`](#info) + - [`Get-Records`](#get-records) + - [`Get-Record`](#get-record) + - [`Get-Controllers`](#get-controllers) + - [`Balance`](#balance) + - [`Balances`](#balances) + - [Write methods](#write-methods) + - [`Transfer`](#transfer) + - [`Set-Record`](#set-record) + - [`Set-Name`](#set-name) + - [`Set-Ticker`](#set-ticker) + - [`Set-Description`](#set-description) + - [`Set-Keywords`](#set-keywords) + - [`Set-Controller`](#set-controller) + - [`Remove-Controller`](#remove-controller) + - [`Remove-Record`](#remove-record) + - [`Release-Name`](#release-name) + - [`Reassign-Name`](#reassign-name) +- [Developers](#developers) + - [Requirements](#requirements) + - [Lua Setup (MacOS)](#lua-setup-macos) + - [LuaRocks Setup](#luarocks-setup) + - [aos](#aos) + - [Code Formatting](#code-formatting) + - [Testing](#testing-1) + - [Dependencies](#dependencies) + + + ## Setup ### Install @@ -67,44 +108,6 @@ yarn aos:spawn This will deploy the bundled lua file to arweave as an L1 transaction, so your wallet will need AR to pay the gas. -### Building the custom module - -Using the ao-dev-cli. - -#### Build - -This will compile the standalone ANT module to wasm, as a file named `process.wasm` and loads the module in [AO Loader](https://github.com/permaweb/ao/tree/main/loader) to validate the WASM program is valid. - -```bash -yarn module:build -``` - -#### Publish - -Publishes the custom ANT module to arweave - requires you placed your JWK in the `tools` directory. May require AR in the wallet to pay gas. - -```sh -yarn module:publish -``` - -#### Load - -Loads the module in [AO Loader](https://github.com/permaweb/ao/tree/main/loader) to validate the WASM program is valid. - -```bash -yarn module:load -``` - -Requires `module:build` to have been called so that `process.wasm` exists. - -#### Spawn - -Spawns a process with the `process.wasm` file. - -```bash -yarn module:spawn -``` - ## Handler Methods For interacting with handlers please refer to the [AO Cookbook] @@ -246,6 +249,26 @@ Removes a record from the ANT. | Action | string | "Remove-Record" | true | Action tag for triggering handler | | Sub-Domain | string | "^(?:[a-zA-Z0-9_-]+\|@)$" | true | Subdomain of the record to remove. | +#### `Release-Name` + +Calls the IO Network process to release the given ArNS name if that name is associated with the ANT. + +| Tag Name | Type | Pattern | Required | Description | +| -------- | ------ | ------------------- | -------- | --------------------------------- | +| Action | string | "Release-Name" | true | Action tag for triggering handler | +| Name | string | "^([a-zA-Z0-9_-])$" | true | ArNS name to release | + +#### `Reassign-Name` + +Calls the IO Network process to reassign the given ArNS name to a new ANT ID if that name is associated with the ANT. + +| Tag Name | Type | Pattern | Required | Description | +| ------------- | ------ | --------------------- | -------- | ---------------------------------------------------- | +| Action | string | "Reassign-Name" | true | Action tag for triggering handler | +| IO-Process-Id | string | "^[a-zA-Z0-9_-]{43}$" | true | ID of the IO Network Process to reassign the name on | +| Process-Id | string | "^[a-zA-Z0-9_-]{43}$" | true | ID of the new ANT to assign to the ArNS name | +| Name | string | "^([a-zA-Z0-9_-])$" | true | Subdomain of the record to remove. | + ## Developers ### Requirements diff --git a/package.json b/package.json index b2c4e45..43d5f1e 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "arweave": "^1.15.1", "husky": "^9.1.6", "lint-staged": "^15.2.10", + "markdown-toc-gen": "^1.1.0", "prettier": "^3.3.2" }, "lint-staged": { @@ -29,6 +30,9 @@ ], "*.{js,mjs}": [ "prettier --write" + ], + "**/README.md": [ + "markdown-toc-gen insert" ] } } diff --git a/yarn.lock b/yarn.lock index 7720d32..a235c60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -499,6 +499,13 @@ resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@keplr-wallet/common@0.12.130": version "0.12.130" resolved "https://registry.yarnpkg.com/@keplr-wallet/common/-/common-0.12.130.tgz#7528c8c6c80bb4f8a6acd8587b1862567d66caed" @@ -717,6 +724,11 @@ dependencies: "@randlabs/communication-bridge" "1.0.1" +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@solana/buffer-layout@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" @@ -870,11 +882,28 @@ ansi-escapes@^7.0.0: dependencies: environment "^1.0.0" +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + ansi-regex@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + ansi-styles@^6.0.0, ansi-styles@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" @@ -1114,6 +1143,14 @@ call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.1" +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" @@ -1142,6 +1179,15 @@ cli-truncate@^4.0.0: slice-ansi "^5.0.0" string-width "^7.0.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + color-convert@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -1149,12 +1195,19 @@ color-convert@^1.9.3: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@^1.0.0: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== @@ -1301,6 +1354,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + elliptic@6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -1332,6 +1390,11 @@ emoji-regex@^10.3.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4" integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + enabled@2.0.x: version "2.0.0" resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" @@ -1366,6 +1429,11 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" +escalade@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + ethers@^6.12.0: version "6.13.3" resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.3.tgz#b87afdadb91cc8df5f56b9c59c96e5b206f4a600" @@ -1465,6 +1533,11 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + get-east-asian-width@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz#21b4071ee58ed04ee0db653371b55b4299875389" @@ -1501,6 +1574,11 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" @@ -1598,6 +1676,11 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-fullwidth-code-point@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" @@ -1658,6 +1741,21 @@ jayson@^4.1.1: uuid "^8.3.2" ws "^7.5.10" +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + js-sha256@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" @@ -1787,6 +1885,14 @@ lru-cache@^10.2.2: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== +markdown-toc-gen@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/markdown-toc-gen/-/markdown-toc-gen-1.1.0.tgz#cf155096ece008fa6d5b440024df3c1462b8bdb6" + integrity sha512-0ej77tAVqxXBzvKYJ0FQ+vVY5BLUK5xASPDscpaOU9Q9vQ+tvzcyQUV1y6wqDkK4A8mcUYD6yT+EqzcVso43ZA== + dependencies: + jest-diff "^29.7.0" + yargs "^17.7.2" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -1975,6 +2081,15 @@ prettier@^3.3.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a" integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -2034,6 +2149,11 @@ ramda@^0.30.0: resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.30.1.tgz#7108ac95673062b060025052cd5143ae8fc605bf" integrity sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw== +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -2064,6 +2184,11 @@ regenerator-runtime@^0.14.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + restore-cursor@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-5.1.0.tgz#0766d95699efacb14150993f55baf0953ea1ebe7" @@ -2215,6 +2340,15 @@ string-argv@~0.3.2: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^7.0.0: version "7.2.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" @@ -2231,6 +2365,13 @@ string_decoder@^1.1.1, string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -2248,6 +2389,13 @@ superstruct@^2.0.2: resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" integrity sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A== +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + symbol-observable@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a" @@ -2438,6 +2586,15 @@ winston@^3.14.1: triple-beam "^1.3.0" winston-transport "^4.7.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e" @@ -2480,11 +2637,34 @@ xstream@^11.14.0: globalthis "^1.0.1" symbol-observable "^2.0.3" +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + yaml@~2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130" integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + zod@^3.22.4, zod@^3.23.5: version "3.23.8" resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" From aa376d0f60666bc42bf3b8584f5a9f4c9675dc70 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Fri, 8 Nov 2024 15:22:39 -0600 Subject: [PATCH 02/28] chore(refactor): refactor handlers to codify notices --- src/common/main.lua | 552 +++++++++-------------------------------- src/common/utils.lua | 95 +++++++ test/registry.test.mjs | 19 +- 3 files changed, 220 insertions(+), 446 deletions(-) diff --git a/src/common/main.lua b/src/common/main.lua index 54664c3..6c4b572 100644 --- a/src/common/main.lua +++ b/src/common/main.lua @@ -6,6 +6,8 @@ function ant.init() local json = require(".common.json") local utils = require(".common.utils") local camel = utils.camelCase + local createActionHandler = utils.createActionHandler + -- spec modules local balances = require(".common.balances") local initialize = require(".common.initialize") @@ -63,103 +65,45 @@ function ant.init() Burn = "Burn", } - Handlers.add( - camel(TokenSpecActionMap.Transfer), - utils.hasMatchingTag("Action", TokenSpecActionMap.Transfer), - function(msg) - local recipient = msg.Tags.Recipient - local function checkAssertions() - utils.validateOwner(msg.From) - end + createActionHandler(TokenSpecActionMap.Transfer, function(msg) + local recipient = msg.Tags.Recipient + utils.validateOwner(msg.From) + balances.transfer(recipient) + if not msg.Cast then + ao.send(utils.notices.debit(msg)) + ao.send(utils.notices.credit(msg)) + end + end) - local inputStatus, inputResult = pcall(checkAssertions) + createActionHandler(TokenSpecActionMap.Balance, function(msg) + local balRes = balances.balance(msg.Tags.Recipient or msg.From) - if not inputStatus then - ao.send({ - Target = msg.From, - Tags = { Action = "Invalid-Transfer-Notice", Error = "Transfer-Error" }, - Data = tostring(inputResult), - ["Message-Id"] = msg.Id, - }) - return - end - local transferStatus, transferResult = pcall(balances.transfer, recipient) - - if not transferStatus then - ao.send({ - Target = msg.From, - Tags = { Action = "Invalid-Transfer-Notice", Error = "Transfer-Error" }, - ["Message-Id"] = msg.Id, - Data = tostring(transferResult), - }) - return - elseif not msg.Cast then - ao.send(utils.notices.debit(msg)) - ao.send(utils.notices.credit(msg)) - utils.notices.notifyState(msg, AntRegistryId) - return - end - ao.send({ - Target = msg.From, - Data = transferResult, - }) - utils.notices.notifyState(msg, AntRegistryId) - end - ) + ao.send({ + Target = msg.From, + Action = "Balance-Notice", + Balance = tostring(balRes), + Ticker = Ticker, + Address = msg.Tags.Recipient or msg.From, + Data = balRes, + }) + end) - Handlers.add( - camel(TokenSpecActionMap.Balance), - utils.hasMatchingTag("Action", TokenSpecActionMap.Balance), - function(msg) - local balStatus, balRes = pcall(balances.balance, msg.Tags.Recipient or msg.From) - if not balStatus then - ao.send({ - Target = msg.From, - Tags = { Action = "Invalid-Balance-Notice", Error = "Balance-Error" }, - ["Message-Id"] = msg.Id, - Data = tostring(balRes), - }) - else - ao.send({ - Target = msg.From, - Action = "Balance-Notice", - Balance = tostring(balRes), - Ticker = Ticker, - Address = msg.Tags.Recipient or msg.From, - Data = balRes, - }) - end - end - ) + createActionHandler(TokenSpecActionMap.Balances, function() + return balances.balances() + end) - Handlers.add( - camel(TokenSpecActionMap.Balances), - utils.hasMatchingTag("Action", TokenSpecActionMap.Balances), - function(msg) - ao.send({ - Target = msg.From, - Action = "Balances-Notice", - Data = balances.balances(), - }) - end - ) + createActionHandler(TokenSpecActionMap.TotalSupply, function(msg) + assert(msg.From ~= ao.id, "Cannot call Total-Supply from the same process!") - Handlers.add( - camel(TokenSpecActionMap.TotalSupply), - utils.hasMatchingTag("Action", TokenSpecActionMap.TotalSupply), - function(msg) - assert(msg.From ~= ao.id, "Cannot call Total-Supply from the same process!") - - ao.send({ - Target = msg.From, - Action = "Total-Supply-Notice", - Data = TotalSupply, - Ticker = Ticker, - }) - end - ) + ao.send({ + Target = msg.From, + Action = "Total-Supply-Notice", + Data = TotalSupply, + Ticker = Ticker, + }) + end) - Handlers.add(camel(TokenSpecActionMap.Info), utils.hasMatchingTag("Action", TokenSpecActionMap.Info), function(msg) + createActionHandler(TokenSpecActionMap.Info, function(msg) local info = { Name = Name, Ticker = Ticker, @@ -182,366 +126,77 @@ function ant.init() -- ActionMap (ANT Spec) - Handlers.add(camel(ActionMap.AddController), utils.hasMatchingTag("Action", ActionMap.AddController), function(msg) - local assertHasPermission, permissionErr = pcall(utils.assertHasPermission, msg.From) - if assertHasPermission == false then - return ao.send({ - Target = msg.From, - Action = "Invalid-Add-Controller-Notice", - Error = "Add-Controller-Error", - ["Message-Id"] = msg.Id, - Data = permissionErr, - }) - end - local controllerStatus, controllerRes = pcall(controllers.setController, msg.Tags.Controller) - if not controllerStatus then - ao.send({ - Target = msg.From, - Action = "Invalid-Add-Controller-Notice", - Error = "Add-Controller-Error", - ["Message-Id"] = msg.Id, - Data = controllerRes, - }) - return - end - ao.send({ Target = msg.From, Action = "Add-Controller-Notice", Data = controllerRes }) - utils.notices.notifyState(msg, AntRegistryId) + createActionHandler(ActionMap.AddController, function(msg) + utils.assertHasPermission(msg.From) + return controllers.setController(msg.Tags.Controller) end) - Handlers.add( - camel(ActionMap.RemoveController), - utils.hasMatchingTag("Action", ActionMap.RemoveController), - function(msg) - local assertHasPermission, permissionErr = pcall(utils.assertHasPermission, msg.From) - if assertHasPermission == false then - return ao.send({ - Target = msg.From, - Action = "Invalid-Remove-Controller-Notice", - Data = permissionErr, - Error = "Remove-Controller-Error", - ["Message-Id"] = msg.Id, - }) - end - local removeStatus, removeRes = pcall(controllers.removeController, msg.Tags.Controller) - if not removeStatus then - ao.send({ - Target = msg.From, - Action = "Invalid-Remove-Controller-Notice", - Data = removeRes, - Error = "Remove-Controller-Error", - ["Message-Id"] = msg.Id, - }) - return - end - - ao.send({ Target = msg.From, Action = "Remove-Controller-Notice", Data = removeRes }) - utils.notices.notifyState(msg, AntRegistryId) - end - ) + createActionHandler(ActionMap.RemoveController, function(msg) + utils.assertHasPermission(msg.From) + return controllers.removeController(msg.Tags.Controller) + end) - Handlers.add(camel(ActionMap.Controllers), utils.hasMatchingTag("Action", ActionMap.Controllers), function(msg) - ao.send({ Target = msg.From, Action = "Controllers-Notice", Data = controllers.getControllers() }) + createActionHandler(ActionMap.Controllers, function() + return controllers.getControllers() end) - Handlers.add(camel(ActionMap.SetRecord), utils.hasMatchingTag("Action", ActionMap.SetRecord), function(msg) - local assertHasPermission, permissionErr = pcall(utils.assertHasPermission, msg.From) - if assertHasPermission == false then - return ao.send({ - Target = msg.From, - Action = "Invalid-Set-Record-Notice", - Data = permissionErr, - Error = "Set-Record-Error", - ["Message-Id"] = msg.Id, - }) - end + createActionHandler(ActionMap.SetRecord, function(msg) + utils.assertHasPermission(msg.From) local tags = msg.Tags local name, transactionId, ttlSeconds = string.lower(tags["Sub-Domain"]), tags["Transaction-Id"], tonumber(tags["TTL-Seconds"]) - local setRecordStatus, setRecordResult = pcall(records.setRecord, name, transactionId, ttlSeconds) - if not setRecordStatus then - ao.send({ - Target = msg.From, - Action = "Invalid-Set-Record-Notice", - Data = setRecordResult, - Error = "Set-Record-Error", - ["Message-Id"] = msg.Id, - }) - return - end - - ao.send({ Target = msg.From, Action = "Set-Record-Notice", Data = setRecordResult }) + return records.setRecord(name, transactionId, ttlSeconds) end) - Handlers.add(camel(ActionMap.RemoveRecord), utils.hasMatchingTag("Action", ActionMap.RemoveRecord), function(msg) - local assertHasPermission, permissionErr = pcall(utils.assertHasPermission, msg.From) - if assertHasPermission == false then - return ao.send({ Target = msg.From, Action = "Invalid-Remove-Record-Notice", Data = permissionErr }) - end - local name = string.lower(msg.Tags["Sub-Domain"]) - local removeRecordStatus, removeRecordResult = pcall(records.removeRecord, name) - if not removeRecordStatus then - ao.send({ - Target = msg.From, - Action = "Invalid-Remove-Record-Notice", - Data = removeRecordResult, - Error = "Remove-Record-Error", - ["Message-Id"] = msg.Id, - }) - else - ao.send({ Target = msg.From, Data = removeRecordResult }) - end + createActionHandler(ActionMap.RemoveRecord, function(msg) + utils.assertHasPermission(msg.From) + return records.removeRecord(string.lower(msg.Tags["Sub-Domain"])) end) - Handlers.add(camel(ActionMap.Record), utils.hasMatchingTag("Action", ActionMap.Record), function(msg) + createActionHandler(ActionMap.Record, function(msg) local name = string.lower(msg.Tags["Sub-Domain"]) - local nameStatus, nameRes = pcall(records.getRecord, name) - if not nameStatus then - ao.send({ - Target = msg.From, - Action = "Invalid-Record-Notice", - Data = nameRes, - Error = "Record-Error", - ["Message-Id"] = msg.Id, - }) - return - end - - local recordNotice = { - Target = msg.From, - Action = "Record-Notice", - Name = name, - Data = nameRes, - } - - -- Add forwarded tags to the credit and debit notice messages - for tagName, tagValue in pairs(msg) do - -- Tags beginning with "X-" are forwarded - if string.sub(tagName, 1, 2) == "X-" then - recordNotice[tagName] = tagValue - end - end - - -- Send Record-Notice - ao.send(recordNotice) + return records.getRecord(name) end) - Handlers.add(camel(ActionMap.Records), utils.hasMatchingTag("Action", ActionMap.Records), function(msg) - local allRecords = records.getRecords() - - -- Credit-Notice message template, that is sent to the Recipient of the transfer - local recordsNotice = { - Target = msg.From, - Action = "Records-Notice", - Data = allRecords, - } - - -- Add forwarded tags to the records notice messages - for tagName, tagValue in pairs(msg) do - -- Tags beginning with "X-" are forwarded - if string.sub(tagName, 1, 2) == "X-" then - recordsNotice[tagName] = tagValue - end - end - - -- Send Records-Notice - ao.send(recordsNotice) + createActionHandler(ActionMap.Records, function() + return records.getRecords() end) - Handlers.add(camel(ActionMap.SetName), utils.hasMatchingTag("Action", ActionMap.SetName), function(msg) - local assertHasPermission, permissionErr = pcall(utils.assertHasPermission, msg.From) - if assertHasPermission == false then - return ao.send({ - Target = msg.From, - Action = "Invalid-Set-Name-Notice", - Data = permissionErr, - Error = "Set-Name-Error", - ["Message-Id"] = msg.Id, - }) - end - local nameStatus, nameRes = pcall(balances.setName, msg.Tags.Name) - if not nameStatus then - ao.send({ - Target = msg.From, - Action = "Invalid-Set-Name-Notice", - Data = nameRes, - Error = "Set-Name-Error", - ["Message-Id"] = msg.Id, - }) - return - end - ao.send({ Target = msg.From, Action = "Set-Name-Notice", Data = nameRes }) + createActionHandler(ActionMap.SetName, function(msg) + utils.assertHasPermission(msg.From) + return balances.setName(msg.Tags.Name) end) - Handlers.add(camel(ActionMap.SetTicker), utils.hasMatchingTag("Action", ActionMap.SetTicker), function(msg) - local assertHasPermission, permissionErr = pcall(utils.assertHasPermission, msg.From) - if assertHasPermission == false then - return ao.send({ - Target = msg.From, - Action = "Invalid-Set-Ticker-Notice", - Data = permissionErr, - Error = "Set-Ticker-Error", - ["Message-Id"] = msg.Id, - }) - end - local tickerStatus, tickerRes = pcall(balances.setTicker, msg.Tags.Ticker) - if not tickerStatus then - ao.send({ - Target = msg.From, - Action = "Invalid-Set-Ticker-Notice", - Data = tickerRes, - Error = "Set-Ticker-Error", - ["Message-Id"] = msg.Id, - }) - return - end - - ao.send({ Target = msg.From, Action = "Set-Ticker-Notice", Data = tickerRes }) + createActionHandler(ActionMap.SetTicker, function(msg) + utils.assertHasPermission(msg.From) + return balances.setTicker(msg.Tags.Ticker) 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 + createActionHandler(ActionMap.SetDescription, function(msg) + utils.assertHasPermission(msg.From) + return balances.setDescription(msg.Tags.Description) + end) - -- Decode JSON from the tag + createActionHandler(ActionMap.SetKeywords, function(msg) + utils.assertHasPermission(msg.From) 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 }) + assert(success and type(keywords) == "table", "Invalid JSON format for keywords") + return balances.setKeywords(keywords) end) - Handlers.add( - camel(ActionMap.InitializeState), - utils.hasMatchingTag("Action", ActionMap.InitializeState), - function(msg) - assert(msg.From == Owner, "Only the owner can initialize the state") - local initStatus, result = pcall(initialize.initializeANTState, msg.Data) + createActionHandler(ActionMap.InitializeState, function(msg) + return initialize.initializeANTState(msg.Data) + end) - if not initStatus then - ao.send({ - Target = msg.From, - Action = "Invalid-Initialize-State-Notice", - Data = result, - Error = "Initialize-State-Error", - ["Message-Id"] = msg.Id, - }) - return - else - ao.send({ Target = msg.From, Action = "Initialize-State-Notice", Data = result }) - utils.notices.notifyState(msg, AntRegistryId) - end - end - ) - Handlers.add(camel(ActionMap.State), utils.hasMatchingTag("Action", ActionMap.State), function(msg) + createActionHandler(ActionMap.State, function(msg) utils.notices.notifyState(msg, msg.From) end) - Handlers.prepend( - camel(ActionMap.Evolve), - Handlers.utils.continue(utils.hasMatchingTag("Action", "Eval")), - function(msg) - local srcCodeTxId = msg.Tags["Source-Code-TX-ID"] - if not srcCodeTxId then - return - end - - if Owner ~= msg.From then - ao.send({ - Target = msg.From, - Action = "Invalid-Evolve-Notice", - Error = "Evolve-Error", - ["Message-Id"] = msg.Id, - Data = "Only the Owner [" .. Owner or "no owner set" .. "] can call Evolve", - }) - return - end - - local srcCodeTxIdStatus = pcall(utils.validateArweaveId, srcCodeTxId) - if not srcCodeTxIdStatus then - ao.send({ - Target = msg.From, - Action = "Invalid-Evolve-Notice", - Error = "Evolve-Error", - ["Message-Id"] = msg.Id, - Data = "Source-Code-TX-ID is required", - }) - return - end - 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 - ao.send({ - Target = msg.From, - Action = "Invalid-Release-Name-Notice", - Data = permissionErr, - Error = "Release-Name-Error", - }) - return - end + createActionHandler(ActionMap.ReleaseName, function(msg) + utils.validateOwner(msg.From) local name = string.lower(msg.Tags["Name"]) local ioProcess = msg.Tags["IO-Process-Id"] @@ -561,26 +216,9 @@ function ant.init() }) end) - Handlers.add(camel(ActionMap.ReassignName), utils.hasMatchingTag("Action", ActionMap.ReassignName), function(msg) - local assertHasPermission, permissionErr = pcall(utils.validateOwner, msg.From) - if assertHasPermission == false then - return ao.send({ - Target = msg.From, - Action = "Invalid-Reassign-Name-Notice", - Data = permissionErr, - Error = "Reassign-Name-Error", - }) - end - - local assertValidProcessId, processErr = pcall(utils.validateArweaveId, msg.Tags["Process-Id"]) - if assertValidProcessId == false then - return ao.send({ - Target = msg.From, - Action = "Invalid-Reassign-Name-Notice", - Data = processErr, - Error = "Reassign-Name-Error", - }) - end + createActionHandler(ActionMap.ReassignName, function(msg) + utils.validateOwner(msg.From) + utils.validateArweaveId(msg.Tags["Process-Id"]) local name = string.lower(msg.Tags["Name"]) local ioProcess = msg.Tags["IO-Process-Id"] @@ -597,12 +235,48 @@ function ant.init() ao.send({ Target = msg.From, + -- This is out of our pattern, should be -Notice Action = "Reassign-Name-Submit-Notice", Initiator = msg.From, Name = name, ["Process-Id"] = antProcessIdToReassign, }) end) + + Handlers.prepend( + camel(ActionMap.Evolve), + Handlers.utils.continue(utils.hasMatchingTag("Action", "Eval")), + function(msg) + local srcCodeTxId = msg.Tags["Source-Code-TX-ID"] + if not srcCodeTxId then + return + end + + if Owner ~= msg.From then + ao.send({ + Target = msg.From, + Action = "Invalid-Evolve-Notice", + Error = "Evolve-Error", + ["Message-Id"] = msg.Id, + Data = "Only the Owner [" .. Owner or "no owner set" .. "] can call Evolve", + }) + return + end + + local srcCodeTxIdStatus = pcall(utils.validateArweaveId, srcCodeTxId) + if not srcCodeTxIdStatus then + ao.send({ + Target = msg.From, + Action = "Invalid-Evolve-Notice", + Error = "Evolve-Error", + ["Message-Id"] = msg.Id, + Data = "Source-Code-TX-ID is required", + }) + return + end + SourceCodeTxId = srcCodeTxId + end + ) end return ant diff --git a/src/common/utils.lua b/src/common/utils.lua index 12784e6..00d796c 100644 --- a/src/common/utils.lua +++ b/src/common/utils.lua @@ -197,6 +197,29 @@ function utils.reply(msg) Handlers.utils.reply(msg) 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 +function utils.deepCopy(original) + if not original then + return nil + end + + if type(original) ~= "table" then + return original + end + + local copy = {} + for key, value in pairs(original) do + if type(value) == "table" then + copy[key] = utils.deepCopy(value) -- Recursively copy the nested table + else + copy[key] = value + end + end + return copy +end + -- NOTE: lua 5.3 has limited regex support, particularly for lookaheads and negative lookaheads or use of {n} function utils.validateUndername(name) local validLength = #name <= constants.MAX_UNDERNAME_LENGTH @@ -260,6 +283,20 @@ end utils.notices = {} +-- @param oldMsg table +-- @param newMsg table +-- Add forwarded tags to the new message +-- @return newMsg table +function utils.notices.addForwardedTags(oldMsg, newMsg) + for tagName, tagValue in pairs(oldMsg) do + -- Tags beginning with "X-" are forwarded + if string.sub(tagName, 1, 2) == "X-" then + newMsg[tagName] = tagValue + end + end + return newMsg +end + function utils.notices.credit(msg) local notice = { Target = msg.Recipient, @@ -307,6 +344,7 @@ function utils.notices.notifyState(msg, target) print("No target specified for state notice") return end + Records = Records or {} local state = { Records = Records, Controllers = Controllers, @@ -342,6 +380,63 @@ function utils.getHandlerNames(handlers) return names end +function utils.errorHandler(err) + return debug.traceback(err) +end + +function utils.createHandler(tagName, tagValue, handler, position) + assert( + type(position) == "string" or type(position) == "nil", + utils.errorHandler("Position must be a string or nil") + ) + assert( + position == nil or position == "add" or position == "prepend" or position == "append", + "Position must be one of 'add', 'prepend', 'append'" + ) + return Handlers[position or "add"]( + utils.camelCase(tagValue), + Handlers.utils.continue(Handlers.utils.hasMatchingTag(tagName, tagValue)), + function(msg) + -- sometimes the message id is not present on dryrun + print("Handling Action [" .. msg.Id or "no-msg-id" .. "]: " .. tagValue) + local prevOwner = tostring(Owner) + local prevControllers = utils.deepCopy(Controllers) + + local handlerStatus, handlerRes = xpcall(function() + return handler(msg) + end, utils.errorHandler) + + if not handlerStatus then + ao.send(utils.notices.addForwardedTags(msg, { + Target = msg.From, + Action = "Invalid-" .. tagValue .. "-Notice", + Error = tagValue .. "-Error", + ["Message-Id"] = msg.Id, + Data = handlerRes, + })) + elseif handlerRes then + ao.send(utils.notices.addForwardedTags(msg, { + Target = msg.From, + Action = tagValue .. "-Notice", + Data = type(handlerRes) == "string" and handlerRes or json.encode(handlerRes), + })) + end + + local hasNewOwner = Owner ~= prevOwner + local hasDifferentControllers = #utils.keys(Controllers) ~= #utils.keys(prevControllers) + if hasNewOwner or hasDifferentControllers then + utils.notices.notifyState(msg, msg.From) + end + + return handlerRes + end + ) +end + +function utils.createActionHandler(action, msgHandler, position) + return utils.createHandler("Action", action, msgHandler, position) +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") diff --git a/test/registry.test.mjs b/test/registry.test.mjs index 441eae6..6709b11 100644 --- a/test/registry.test.mjs +++ b/test/registry.test.mjs @@ -45,19 +45,22 @@ describe('Registry Updates', async () => { }); it('should send update to registry when a controller is removed', async () => { - await handle({ + const addControllerRes = await handle({ Tags: [ { name: 'Action', value: 'Add-Controller' }, { name: 'Controller', value: controllerAddress }, ], }); - const result = await handle({ - Tags: [ - { name: 'Action', value: 'Remove-Controller' }, - { name: 'Controller', value: controllerAddress }, - ], - }); + const result = await handle( + { + Tags: [ + { name: 'Action', value: 'Remove-Controller' }, + { name: 'Controller', value: controllerAddress }, + ], + }, + addControllerRes.Memory, + ); const message = result.Messages[0]?.Tags.find( (tag) => @@ -68,6 +71,7 @@ describe('Registry Updates', async () => { const notifyMessage = result.Messages[1]?.Tags.find( (tag) => tag.name === 'Action' && tag.value === 'State-Notice', ); + assert(notifyMessage, 'State-Notice message not found'); }); @@ -88,6 +92,7 @@ describe('Registry Updates', async () => { const notifyMessage = result.Messages[2]?.Tags.find( (tag) => tag.name === 'Action' && tag.value === 'State-Notice', ); + assert(notifyMessage, 'State-Notice message not found'); }); }); From f66b13a11e1de6e6061f43875251fec23900b210 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Fri, 8 Nov 2024 15:24:00 -0600 Subject: [PATCH 03/28] chore(refactor): refactor handlers to codify notices --- src/common/main.lua | 1 + src/common/utils.lua | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/main.lua b/src/common/main.lua index 6c4b572..f1d5682 100644 --- a/src/common/main.lua +++ b/src/common/main.lua @@ -15,6 +15,7 @@ function ant.init() local controllers = require(".common.controllers") Owner = Owner or ao.env.Process.Owner + Records = Records or {} Balances = Balances or { [Owner] = 1 } Controllers = Controllers or { Owner } diff --git a/src/common/utils.lua b/src/common/utils.lua index 00d796c..bba0ae3 100644 --- a/src/common/utils.lua +++ b/src/common/utils.lua @@ -344,7 +344,7 @@ function utils.notices.notifyState(msg, target) print("No target specified for state notice") return end - Records = Records or {} + local state = { Records = Records, Controllers = Controllers, From f3e7b72b8799ac47e5cfa79729e9455a6cbb6dca Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Fri, 8 Nov 2024 15:24:12 -0600 Subject: [PATCH 04/28] chore(refactor): refactor handlers to codify notices --- src/common/main.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/main.lua b/src/common/main.lua index f1d5682..6c4b572 100644 --- a/src/common/main.lua +++ b/src/common/main.lua @@ -15,7 +15,6 @@ function ant.init() local controllers = require(".common.controllers") Owner = Owner or ao.env.Process.Owner - Records = Records or {} Balances = Balances or { [Owner] = 1 } Controllers = Controllers or { Owner } From fa4fd72e706422d9627d1ba3ecdb397c098502cd Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Sat, 9 Nov 2024 11:01:57 -0600 Subject: [PATCH 05/28] chore(docs): add docs to ant apis --- src/common/balances.lua | 35 ++++++ src/common/controllers.lua | 8 ++ src/common/main.lua | 35 +++++- src/common/records.lua | 25 +++-- src/common/types.lua | 48 +++++++++ src/common/utils.lua | 211 +++++++++++++++++++++++-------------- 6 files changed, 276 insertions(+), 86 deletions(-) create mode 100644 src/common/types.lua diff --git a/src/common/balances.lua b/src/common/balances.lua index 5047cbb..297d1f1 100644 --- a/src/common/balances.lua +++ b/src/common/balances.lua @@ -1,42 +1,66 @@ +--- Module for managing balances and transactions. +-- @module balances +require(".common.types") local utils = require(".common.utils") local json = require(".common.json") local balances = {} +--- Checks if a wallet has sufficient balance. +---@param wallet string - The wallet address to check. +---@return boolean - Returns true if the wallet has a balance greater than 0, otherwise false. function balances.walletHasSufficientBalance(wallet) return Balances[wallet] ~= nil and Balances[wallet] > 0 end +--- Transfers the ANT to a specified wallet. +---@param to string - The wallet address to transfer the balance to. +---@return string - Returns the encoded JSON representation of the transferred balance. function balances.transfer(to) utils.validateArweaveId(to) Balances = { [to] = 1 } + --luacheck: ignore Owner Controllers Owner = to Controllers = {} return json.encode({ [to] = 1 }) end +--- Retrieves the balance of a specified wallet. +---@param address string - The wallet address to retrieve the balance from. +---@return number - Returns the balance of the specified wallet. function balances.balance(address) utils.validateArweaveId(address) local balance = Balances[address] or 0 return balance end +--- Retrieves all balances. +---@return string - Returns the encoded JSON representation of all balances. function balances.balances() return json.encode(Balances) end +--- Sets the name of the ANT. +---@param name string - The name to set. +---@return string - Returns the encoded JSON representation of the updated name. function balances.setName(name) assert(type(name) == "string", "Name must be a string") Name = name return json.encode({ name = Name }) end +--- Sets the ticker of the ANT. +---@param ticker string - The ticker to set. +---@return string - Returns the encoded JSON representation of the updated ticker. function balances.setTicker(ticker) assert(type(ticker) == "string", "Ticker must be a string") Ticker = ticker return json.encode({ ticker = Ticker }) end +--- Sets the description of the ANT. +---@param description string - The description to set. +---@return string - Returns the encoded JSON representation of the updated description. function balances.setDescription(description) assert(type(description) == "string", "Description must be a string") assert(#description <= 512, "Description must not be longer than 512 characters") @@ -44,6 +68,9 @@ function balances.setDescription(description) return json.encode({ description = Description }) end +--- Sets the keywords of the ANT. +---@param keywords table - The keywords to set. +---@return string - Returns the encoded JSON representation of the updated keywords. function balances.setKeywords(keywords) utils.validateKeywords(keywords) @@ -51,4 +78,12 @@ function balances.setKeywords(keywords) return json.encode({ keywords = Keywords }) end +--- Sets the logo of the ANT. +---@param logo string - The Arweave transaction ID that represents the logo. +---@return string - Returns the encoded JSON representation of the updated logo. +function balances.setLogo(logo) + Logo = logo + return json.encode({ logo = Logo }) +end + return balances diff --git a/src/common/controllers.lua b/src/common/controllers.lua index 51da100..a7bc84d 100644 --- a/src/common/controllers.lua +++ b/src/common/controllers.lua @@ -3,6 +3,9 @@ local utils = require(".common.utils") local controllers = {} +--- Set a controller. +---@param controller string The controller to set. +---@return string The encoded JSON representation of the updated controllers. function controllers.setController(controller) utils.validateArweaveId(controller) @@ -14,6 +17,9 @@ function controllers.setController(controller) return json.encode(Controllers) end +--- Remove a controller. +---@param controller string The controller to remove. +---@return string The encoded JSON representation of the updated controllers. function controllers.removeController(controller) utils.validateArweaveId(controller) local controllerExists = false @@ -30,6 +36,8 @@ function controllers.removeController(controller) return json.encode(Controllers) end +--- Get all controllers. +---@return string The encoded JSON representation of the controllers. function controllers.getControllers() return json.encode(Controllers) end diff --git a/src/common/main.lua b/src/common/main.lua index 6c4b572..44c53cb 100644 --- a/src/common/main.lua +++ b/src/common/main.lua @@ -1,3 +1,4 @@ +require(".common.types") local ant = {} function ant.init() @@ -14,20 +15,45 @@ function ant.init() local records = require(".common.records") local controllers = require(".common.controllers") + ---@alias Owner string + ---@description The owner of the ANT Owner = Owner or ao.env.Process.Owner + ---@alias Balances table + ---@description The list of balances for the ANT Balances = Balances or { [Owner] = 1 } + ---@alias Controllers table + ---@description The list of controllers for the ANT Controllers = Controllers or { Owner } + ---@alias Name string + ---@description The name of the ANT Name = Name or "Arweave Name Token" + ---@alias Ticker string + ---@description The ticker symbol of the ANT Ticker = Ticker or "ANT" + ---@alias Logo string + ---@description Arweave transaction ID that is the logo of the ANT Logo = Logo or "Sie_26dvgyok0PZD_-iQAFOhOd5YxDTkczOLoqTTL_A" + ---@alias Description string + ---@description A brief description of this ANT up to 255 characters Description = Description or "A brief description of this ANT." + ---@alias Keywords table + ---@description A list of keywords that describe this ANT. Each keyword must be a string, unique, and less than 32 characters. There can be up to 16 keywords Keywords = Keywords or {} + ---@alias Denomination integer + ---@description The denomination of the ANT - this is set to 0 to denote integer values Denomination = Denomination or 0 + ---@alias TotalSupply integer + ---@description The total supply of the ANT - this is set to 1 to denote single ownership TotalSupply = TotalSupply or 1 + ---@alias Initialized boolean + ---@description Whether the ANT has been initialized with the Initialized = Initialized or false - -- INSERT placeholder used by build script to inject the appropriate ID + ---@alias SourceCodeTxId string + ---@description The Arweave ID of the lua source the ANT currently uses. INSERT placeholder used by build script to inject the appropriate ID SourceCodeTxId = SourceCodeTxId or "__INSERT_SOURCE_CODE_ID__" + ---@alias AntRegistryId string + ---@description The Arweave ID of the ANT Registry contract that this ANT is registered with AntRegistryId = AntRegistryId or ao.env.Process.Tags["ANT-Registry-Id"] or nil local ActionMap = { @@ -40,6 +66,7 @@ function ant.init() SetTicker = "Set-Ticker", SetDescription = "Set-Description", SetKeywords = "Set-Keywords", + SetLogo = "Set-Logo", --- initialization method for bootstrapping the contract from other platforms --- InitializeState = "Initialize-State", -- read @@ -185,6 +212,12 @@ function ant.init() return balances.setKeywords(keywords) end) + createActionHandler(ActionMap.SetLogo, function(msg) + utils.assertHasPermission(msg.From) + utils.validateArweaveId(msg.Logo) + return balances.setLogo(msg.Logo) + end) + createActionHandler(ActionMap.InitializeState, function(msg) return initialize.initializeANTState(msg.Data) end) diff --git a/src/common/records.lua b/src/common/records.lua index a9f0039..9237e7c 100644 --- a/src/common/records.lua +++ b/src/common/records.lua @@ -4,13 +4,15 @@ local records = {} -- defaults to landing page txid Records = Records or { ["@"] = { transactionId = "-k7t8xMoB8hW482609Z9F4bTFMC3MnuW8bTvTyT8pFI", ttlSeconds = 3600 } } +--- Set a record in the Records of the ANT. +---@param name string The name of the record. +---@param transactionId string The transaction ID of the record. +---@param ttlSeconds number The time-to-live in seconds for the record. +---@return string The encoded JSON representation of the record. function records.setRecord(name, transactionId, ttlSeconds) - local nameValidity, nameValidityError = pcall(utils.validateUndername, name) - assert(nameValidity ~= false, nameValidityError) - local targetIdValidity, targetValidityError = pcall(utils.validateArweaveId, transactionId) - assert(targetIdValidity ~= false, targetValidityError) - local ttlSecondsValidity, ttlValidityError = pcall(utils.validateTTLSeconds, ttlSeconds) - assert(ttlSecondsValidity ~= false, ttlValidityError) + utils.validateUndername(name) + utils.validateArweaveId(transactionId) + utils.validateTTLSeconds(ttlSeconds) local recordsCount = #Records @@ -29,13 +31,18 @@ function records.setRecord(name, transactionId, ttlSeconds) }) end +--- Remove a record from the ANT. +---@param name string The name of the record to remove. +---@return string The encoded JSON representation of the deletion message. function records.removeRecord(name) - local nameValidity, nameValidityError = pcall(utils.validateUndername, name) - assert(nameValidity ~= false, nameValidityError) + utils.validateUndername(name) Records[name] = nil return json.encode({ message = "Record deleted" }) end +--- Get a record from the ANT. +---@param name string The name of the record to retrieve. +---@return string The encoded JSON representation of the record. function records.getRecord(name) utils.validateUndername(name) assert(Records[name] ~= nil, "Record does not exist") @@ -43,6 +50,8 @@ function records.getRecord(name) return json.encode(Records[name]) end +--- Get all records from the ANT +---@return string The encoded JSON representation of all records. function records.getRecords() return json.encode(Records) end diff --git a/src/common/types.lua b/src/common/types.lua new file mode 100644 index 0000000..fad8818 --- /dev/null +++ b/src/common/types.lua @@ -0,0 +1,48 @@ +---@alias AoMessage { +--- Id: string, +--- From: string, +--- Target: string, +--- Tags: table, +--- Owner: string, +--- Data: string | number | nil, +--- [string]: string | number, +---} + +---@alias Handler { +--- name: string, +--- pattern: function|table, +--- handle: function, +---} +---@alias HandlersList table + +---@alias Handlers { +--- list: HandlersList, +--- add: function, +--- before: function, +--- after: function, +--- remove: function, +--- prepend: function, +--- append: function, +--- evaluate: function, +---} + +---@alias Record { +--- transactionId: string, +--- ttlSeconds: integer, +---} + +---@alias AntState { +--- Name: string, +--- Ticker: string, +--- Description: string, +--- Keywords: string, +--- Logo: string, +--- Balances: table, +--- Owner: string, +--- Controllers: table, +--- Denomination: integer, +--- TotalSupply: integer, +--- Initialized: boolean, +--- ["Source-Code-TX-ID"|SourceCodeTxId]: string, +--- Records: table, +---} diff --git a/src/common/utils.lua b/src/common/utils.lua index ac484dc..f0d3bf7 100644 --- a/src/common/utils.lua +++ b/src/common/utils.lua @@ -1,5 +1,5 @@ -- the majority of this file came from https://github.com/permaweb/aos/blob/main/process/utils.lua - +require(".common.types") local constants = require(".common.constants") local json = require(".common.json") local utils = { _version = "0.0.1" } @@ -19,8 +19,8 @@ local function isArray(table) return false end --- @param {function} fn --- @param {number} arity +--- @param fn function +--- @param arity number utils.curry = function(fn, arity) assert(type(fn) == "function", "function is required as first argument") arity = arity or debug.getinfo(fn, "u").nparams @@ -42,8 +42,8 @@ utils.curry = function(fn, arity) end --- Concat two Array Tables. --- @param {table} a --- @param {table} b +--- @param a table +--- @param b table utils.concat = utils.curry(function(a, b) assert(type(a) == "table", "first argument should be a table that is an array") assert(type(b) == "table", "second argument should be a table that is an array") @@ -61,9 +61,9 @@ utils.concat = utils.curry(function(a, b) end, 2) --- reduce applies a function to a table --- @param {function} fn --- @param {any} initial --- @param {table} t +--- @param fn function +--- @param initial any +--- @param t table utils.reduce = utils.curry(function(fn, initial, t) assert(type(fn) == "function", "first argument should be a function that accepts (result, value, key)") assert(type(t) == "table" and isArray(t), "third argument should be a table that is an array") @@ -78,8 +78,8 @@ utils.reduce = utils.curry(function(fn, initial, t) return result end, 3) --- @param {function} fn --- @param {table} data +--- @param fn function +--- @param data table utils.map = utils.curry(function(fn, data) assert(type(fn) == "function", "first argument should be a unary function") assert(type(data) == "table" and isArray(data), "second argument should be an Array") @@ -92,13 +92,13 @@ utils.map = utils.curry(function(fn, data) return utils.reduce(map, {}, data) end, 2) --- @param {function} fn --- @param {table} data +--- @param fn function +--- @param data table utils.filter = utils.curry(function(fn, data) assert(type(fn) == "function", "first argument should be a unary function") assert(type(data) == "table" and isArray(data), "second argument should be an Array") - local function filter(result, v, _k) + local function filter(result, v) if fn(v) then table.insert(result, v) end @@ -108,8 +108,8 @@ utils.filter = utils.curry(function(fn, data) return utils.reduce(filter, {}, data) end, 2) --- @param {function} fn --- @param {table} t +--- @param fn function +--- @param t table utils.find = utils.curry(function(fn, t) assert(type(fn) == "function", "first argument should be a unary function") assert(type(t) == "table", "second argument should be a table that is an array") @@ -120,9 +120,9 @@ utils.find = utils.curry(function(fn, t) end end, 2) --- @param {string} propName --- @param {string} value --- @param {table} object +--- @param propName string +--- @param value string +--- @param object table utils.propEq = utils.curry(function(propName, value, object) assert(type(propName) == "string", "first argument should be a string") -- assert(type(value) == "string", "second argument should be a string") @@ -131,7 +131,7 @@ utils.propEq = utils.curry(function(propName, value, object) return object[propName] == value end, 3) --- @param {table} data +--- @param data table utils.reverse = function(data) assert(type(data) == "table", "argument needs to be a table that is an array") return utils.reduce(function(result, v, i) @@ -140,12 +140,13 @@ utils.reverse = function(data) end, {}, data) end --- @param {function} ... +--- @param ... function utils.compose = utils.curry(function(...) local mutations = utils.reverse({ ... }) return function(v) local result = v + ---@diagnostic disable-next-line for _, fn in pairs(mutations) do assert(type(fn) == "function", "each argument needs to be a function") result = fn(result) @@ -154,14 +155,14 @@ utils.compose = utils.curry(function(...) end end, 2) --- @param {string} propName --- @param {table} object +--- @param propName string +--- @param object table utils.prop = utils.curry(function(propName, object) return object[propName] end, 2) --- @param {any} val --- @param {table} t +--- @param val any +--- @param t table utils.includes = utils.curry(function(val, t) assert(type(t) == "table", "argument needs to be a table") return utils.find(function(v) @@ -169,7 +170,7 @@ utils.includes = utils.curry(function(val, t) end, t) ~= nil end, 2) --- @param {table} t +--- @param t table utils.keys = function(t) assert(type(t) == "table", "argument needs to be a table") local keys = {} @@ -179,7 +180,7 @@ utils.keys = function(t) return keys end --- @param {table} t +--- @param t table utils.values = function(t) assert(type(t) == "table", "argument needs to be a table") local values = {} @@ -193,7 +194,9 @@ function utils.hasMatchingTag(tag, value) return Handlers.utils.hasMatchingTag(tag, value) end +---@param msg AoMessage function utils.reply(msg) + ---@diagnostic disable-next-line Handlers.utils.reply(msg) end @@ -221,6 +224,12 @@ function utils.deepCopy(original) end -- 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 +---@example +---```lua +---utils.validateUndername("my-undername") +---``` function utils.validateUndername(name) local validLength = #name <= constants.MAX_UNDERNAME_LENGTH local validRegex = string.match(name, "^@$") ~= nil @@ -229,6 +238,12 @@ function utils.validateUndername(name) assert(valid, constants.UNDERNAME_DOES_NOT_EXIST_MESSAGE) end +---@param id string +---@description Asserts that the provided id is a valid arweave id +---@example +---```lua +---utils.validateArweaveId("QWERTYUIOPASDFGHJKLZXCVBNM1234567890_-") +---``` function utils.validateArweaveId(id) -- the provided id matches the regex, and is not nil local validLength = #id == 43 @@ -237,11 +252,23 @@ function utils.validateArweaveId(id) assert(valid, constants.INVALID_ARWEAVE_ID_MESSAGE) end +---@param ttl integer +---@description Asserts that the ttl is a valid number +---@example +---```lua +---utils.validateTTLSeconds(3600) +---``` function utils.validateTTLSeconds(ttl) local valid = type(ttl) == "number" and ttl >= constants.MIN_TTL_SECONDS and ttl <= constants.MAX_TTL_SECONDS assert(valid, constants.INVALID_TTL_MESSAGE) end +---@param caller string +---@description Asserts that the caller is the owner +---@example +---```lua +---utils.validateOwner(msg.From) +---``` function utils.validateOwner(caller) local isOwner = false if Owner == caller or Balances[caller] or ao.env.Process.Id == caller then @@ -250,6 +277,12 @@ function utils.validateOwner(caller) assert(isOwner, "Sender is not the owner.") end +--- @param from string +--- @description Asserts that the caller is the owner or a controller +--- @example +--- ```lua +--- utils.assertHasPermission(msg.From) +--- ``` function utils.assertHasPermission(from) for _, c in ipairs(Controllers) do if c == from then @@ -281,12 +314,32 @@ function utils.camelCase(str) return str end +---@description gets the state of the relavent ANT globals +---@return AntState +function utils.getState() + return { + Records = Records, + Controllers = Controllers, + Balances = Balances, + Owner = Owner, + Name = Name, + Ticker = Ticker, + Logo = Logo, + Description = Description, + Keywords = Keywords, + Denomination = Denomination, + TotalSupply = TotalSupply, + Initialized = Initialized, + ["Source-Code-TX-ID"] = SourceCodeTxId, + } +end + utils.notices = {} --- @param oldMsg table --- @param newMsg table --- Add forwarded tags to the new message --- @return newMsg table +--- @param oldMsg AoMessage +--- @param newMsg AoMessage +--- @description Add forwarded tags to the new message +--- @return AoMessage function utils.notices.addForwardedTags(oldMsg, newMsg) for tagName, tagValue in pairs(oldMsg) do -- Tags beginning with "X-" are forwarded @@ -297,81 +350,57 @@ function utils.notices.addForwardedTags(oldMsg, newMsg) return newMsg end +--- @param msg AoMessage +--- @description Create a credit notice message +--- @return AoMessage function utils.notices.credit(msg) - local notice = { + return utils.notices.addForwardedTags(msg, { Target = msg.Recipient, Action = "Credit-Notice", Sender = msg.From, Quantity = tostring(1), - } - for tagName, tagValue in pairs(msg) do - -- Tags beginning with "X-" are forwarded - if string.sub(tagName, 1, 2) == "X-" then - notice[tagName] = tagValue - end - end - - return notice + }) end +--- @param msg AoMessage +--- @description Create a debit notice message +--- @return AoMessage function utils.notices.debit(msg) - local notice = { + return utils.notices.addForwardedTags(msg, { Target = msg.From, Action = "Debit-Notice", Recipient = msg.Recipient, Quantity = tostring(1), - } - -- Add forwarded tags to the credit and debit notice messages - for tagName, tagValue in pairs(msg) do - -- Tags beginning with "X-" are forwarded - if string.sub(tagName, 1, 2) == "X-" then - notice[tagName] = tagValue - end - end - - return notice + }) end --- @param notices table +--- @param notices table function utils.notices.sendNotices(notices) for _, notice in ipairs(notices) do ao.send(notice) end end +--- @param msg AoMessage +--- @param target string +--- @description Notify the target of the current state +--- @return nil function utils.notices.notifyState(msg, target) if not target then print("No target specified for state notice") return end - local state = { - Records = Records, - Controllers = Controllers, - Balances = Balances, - Owner = Owner, - Name = Name, - Ticker = Ticker, - Logo = Logo, - Description = Description, - Keywords = Keywords, - Denomination = Denomination, - TotalSupply = TotalSupply, - Initialized = Initialized, - ["Source-Code-TX-ID"] = SourceCodeTxId, - } - - -- Add forwarded tags to the records notice messages - for tagName, tagValue in pairs(msg) do - -- Tags beginning with "X-" are forwarded - if string.sub(tagName, 1, 2) == "X-" then - state[tagName] = tagValue - end - end - - ao.send({ Target = target, Action = "State-Notice", Data = json.encode(state) }) + ao.send(utils.notices.addForwardedTags(msg, { + Target = target, + Action = "State-Notice", + Data = json.encode(utils.getState()), + })) end +--- @param handlers Handlers +--- @description Get the names of all handlers +--- @return table function utils.getHandlerNames(handlers) local names = {} for _, handler in ipairs(handlers.list) do @@ -380,10 +409,26 @@ function utils.getHandlerNames(handlers) return names end +--- @param err any +--- @description Error handler for xpcall +--- @return string function utils.errorHandler(err) return debug.traceback(err) end +---@param tagName string +---@param tagValue string +---@param handler function +---@param position "add" | "prepend" | "append" | nil +---@description +---Creates a handler for a specific tag +---If the handler returns a string, it will be sent as a notice to the sender +---If the Owner or Controllers change, a state notice will be sent to the sender +---If a handler throws an error, an error notice will be sent to the sender +---@example +---```lua +---utils.createHandler("Action", "InitializeState", function(msg) print("Initializing state") end) +---``` function utils.createHandler(tagName, tagValue, handler, position) assert( type(position) == "string" or type(position) == "nil", @@ -401,6 +446,7 @@ function utils.createHandler(tagName, tagValue, handler, position) print("Handling Action [" .. msg.Id or "no-msg-id" .. "]: " .. tagValue) local prevOwner = tostring(Owner) local prevControllers = utils.deepCopy(Controllers) + assert(prevControllers, "Unable to deep copy controllers") local handlerStatus, handlerRes = xpcall(function() return handler(msg) @@ -433,10 +479,21 @@ function utils.createHandler(tagName, tagValue, handler, position) ) end +---@param action string +---@param msgHandler function +---@param position "add" | "prepend" | "append" | nil function utils.createActionHandler(action, msgHandler, position) return utils.createHandler("Action", action, msgHandler, position) end +---@param keywords table +---@description Validates the keywords +---Amount of keywords must be less than or equal to 16 +---Each keyword must be a unique string of 32 characters or less +---@example +---```lua +---utils.validateKeywords({"keyword1", "keyword2"}) +---``` function utils.validateKeywords(keywords) assert(type(keywords) == "table", "Keywords must be an array") assert(#keywords <= 16, "There must not be more than 16 keywords") From 165c6d993cf26200c89e736a6fc4ece77d683581 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Sat, 9 Nov 2024 11:04:30 -0600 Subject: [PATCH 06/28] fix(changelog): update changelog with new changes --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95c7c21..42a1783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Allows for setting the description of the ANT - Set-Keywords Handler - Allows for setting keywords on the ANT +- Set-Logo Handler + - Allows for setting the logo of the ANT + +### Changed + +- Refactored handlers to use a util that codifies responses on calls. +- Added documentation with luadoc types for improved linting. + +### Fixed + +- Fixed the Remove-Record api to return appropriate notices on calls. From 38a9731c22c533cc03e6a7b4b05040ac0c939178 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Sat, 9 Nov 2024 13:29:57 -0600 Subject: [PATCH 07/28] fix(tests): add logo test --- test/balances.test.mjs | 24 ++++++++++++++++++++++++ test/utils.mjs | 6 ++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/test/balances.test.mjs b/test/balances.test.mjs index 28fcbbf..d876ce6 100644 --- a/test/balances.test.mjs +++ b/test/balances.test.mjs @@ -22,6 +22,17 @@ describe('aos Balances', async () => { ); } + async function getInfo(mem) { + const result = await handle( + { + Tags: [{ name: 'Action', value: 'Info' }], + }, + mem, + ); + + return JSON.parse(result.Messages[0].Data); + } + it('should fetch the owner balance', async () => { const result = await handle({ Tags: [ @@ -99,4 +110,17 @@ describe('aos Balances', async () => { ), ); }); + + it('should set the logo of the ant', async () => { + const logo = 'my-logo-'.padEnd(43, '0'); + const result = await handle({ + Tags: [ + { name: 'Action', value: 'Set-Logo' }, + { name: 'Logo', value: logo }, + ], + }); + + const info = await getInfo(result.Memory); + assert(info.Logo === logo, 'Failed to set logo'); + }); }); diff --git a/test/utils.mjs b/test/utils.mjs index 50ed2bd..dd47ec3 100644 --- a/test/utils.mjs +++ b/test/utils.mjs @@ -11,7 +11,7 @@ import { * Loads the aos wasm binary and returns the handle function with program memory * @returns {Promise<{handle: Function, memory: WebAssembly.Memory}>} */ -async function createAosLoader(params) { +export async function createAosLoader(params) { const handle = await AoLoader(AOS_WASM, AO_LOADER_OPTIONS); const evalRes = await handle( null, @@ -24,8 +24,6 @@ async function createAosLoader(params) { }; } -async function createAntAosLoader() { +export async function createAntAosLoader() { return createAosLoader(ANT_EVAL_OPTIONS); } - -export { createAntAosLoader, createAosLoader }; From e7c0bc8f1addcc11f67fd6697fec9bf0d5c9d183 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Sat, 9 Nov 2024 13:35:03 -0600 Subject: [PATCH 08/28] fix(types): make types file a module --- src/common/types.lua | 4 ++++ test/balances.test.mjs | 2 +- test/utils.mjs | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/types.lua b/src/common/types.lua index fad8818..f92ca32 100644 --- a/src/common/types.lua +++ b/src/common/types.lua @@ -1,3 +1,5 @@ +-- need to specify this as a module so it can be imported +local types = {} ---@alias AoMessage { --- Id: string, --- From: string, @@ -46,3 +48,5 @@ --- ["Source-Code-TX-ID"|SourceCodeTxId]: string, --- Records: table, ---} + +return types diff --git a/test/balances.test.mjs b/test/balances.test.mjs index d876ce6..8903fde 100644 --- a/test/balances.test.mjs +++ b/test/balances.test.mjs @@ -40,7 +40,7 @@ describe('aos Balances', async () => { { name: 'Recipient', value: STUB_ADDRESS }, ], }); - + console.dir(result, { depth: null }); const ownerBalance = result.Messages[0].Data; assert(ownerBalance === 1); }); diff --git a/test/utils.mjs b/test/utils.mjs index dd47ec3..35c0364 100644 --- a/test/utils.mjs +++ b/test/utils.mjs @@ -18,6 +18,7 @@ export async function createAosLoader(params) { { ...DEFAULT_HANDLE_OPTIONS, ...params }, AO_LOADER_HANDLER_ENV, ); + return { handle, memory: evalRes.Memory, From d843ec4f4adc2708a86030b1a52cc56e6f95cea2 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Sat, 9 Nov 2024 13:37:40 -0600 Subject: [PATCH 09/28] fix(test): add setLogo to handlers test --- test/info.test.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/info.test.mjs b/test/info.test.mjs index be00279..d41cc8c 100644 --- a/test/info.test.mjs +++ b/test/info.test.mjs @@ -56,6 +56,7 @@ describe('aos Info', async () => { 'setTicker', 'setDescription', 'setKeywords', + 'setLogo', 'initializeState', 'state', 'releaseName', From cc20d607014d4895de5c7141d2d32da0052f016f Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Mon, 11 Nov 2024 07:34:06 -0600 Subject: [PATCH 10/28] fix(format): remove exports on types file, remove log in test --- src/common/balances.lua | 2 +- src/common/main.lua | 3 +-- src/common/types.lua | 3 --- src/common/utils.lua | 1 - test/balances.test.mjs | 1 - 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/common/balances.lua b/src/common/balances.lua index 297d1f1..1c4f69d 100644 --- a/src/common/balances.lua +++ b/src/common/balances.lua @@ -1,6 +1,6 @@ --- Module for managing balances and transactions. -- @module balances -require(".common.types") + local utils = require(".common.utils") local json = require(".common.json") diff --git a/src/common/main.lua b/src/common/main.lua index 44c53cb..6ec0b60 100644 --- a/src/common/main.lua +++ b/src/common/main.lua @@ -1,4 +1,3 @@ -require(".common.types") local ant = {} function ant.init() @@ -172,7 +171,7 @@ function ant.init() local tags = msg.Tags local name, transactionId, ttlSeconds = string.lower(tags["Sub-Domain"]), tags["Transaction-Id"], tonumber(tags["TTL-Seconds"]) - + assert(ttlSeconds, "Missing ttl seconds") return records.setRecord(name, transactionId, ttlSeconds) end) diff --git a/src/common/types.lua b/src/common/types.lua index f92ca32..548d320 100644 --- a/src/common/types.lua +++ b/src/common/types.lua @@ -1,5 +1,4 @@ -- need to specify this as a module so it can be imported -local types = {} ---@alias AoMessage { --- Id: string, --- From: string, @@ -48,5 +47,3 @@ local types = {} --- ["Source-Code-TX-ID"|SourceCodeTxId]: string, --- Records: table, ---} - -return types diff --git a/src/common/utils.lua b/src/common/utils.lua index f0d3bf7..044e6dd 100644 --- a/src/common/utils.lua +++ b/src/common/utils.lua @@ -1,5 +1,4 @@ -- the majority of this file came from https://github.com/permaweb/aos/blob/main/process/utils.lua -require(".common.types") local constants = require(".common.constants") local json = require(".common.json") local utils = { _version = "0.0.1" } diff --git a/test/balances.test.mjs b/test/balances.test.mjs index 8903fde..6ca3007 100644 --- a/test/balances.test.mjs +++ b/test/balances.test.mjs @@ -40,7 +40,6 @@ describe('aos Balances', async () => { { name: 'Recipient', value: STUB_ADDRESS }, ], }); - console.dir(result, { depth: null }); const ownerBalance = result.Messages[0].Data; assert(ownerBalance === 1); }); From 883021e6b1cb4e0efa8bf8136f79b78b94ebd80c Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Mon, 11 Nov 2024 11:40:37 -0600 Subject: [PATCH 11/28] fix(tests): add test for total supply --- test/balances.test.mjs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/balances.test.mjs b/test/balances.test.mjs index 6ca3007..bdc99e9 100644 --- a/test/balances.test.mjs +++ b/test/balances.test.mjs @@ -33,6 +33,17 @@ describe('aos Balances', async () => { return JSON.parse(result.Messages[0].Data); } + async function getTotalSupply(mem) { + const result = await handle( + { + Tags: [{ name: 'Action', value: 'Total-Supply' }], + }, + mem, + ); + + return result.Messages[0].Data; + } + it('should fetch the owner balance', async () => { const result = await handle({ Tags: [ @@ -122,4 +133,8 @@ describe('aos Balances', async () => { const info = await getInfo(result.Memory); assert(info.Logo === logo, 'Failed to set logo'); }); + it('should get total supply', async () => { + const res = await getTotalSupply(); + assert(res, 'total supply should be equal to 1'); + }); }); From 9a1fc1ddcac8b6e96b7ccca88cc74d763618cb6f Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Mon, 11 Nov 2024 13:08:53 -0600 Subject: [PATCH 12/28] 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 13/28] 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 14/28] 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 15/28] 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 16/28] 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 17/28] 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 18/28] 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 19/28] 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, "@") From 9c8b2e9726f925eb3fffdedf89d0a2e423f1412e Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Tue, 12 Nov 2024 15:48:33 -0600 Subject: [PATCH 20/28] fix(apis): do not encode on return in apis --- package.json | 3 ++- src/common/balances.lua | 31 +++++++++++++++---------------- src/common/controllers.lua | 13 ++++++------- src/common/records.lua | 19 +++++++++---------- 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 43d5f1e..73dd92a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "aos:load": "node tools/bundle-aos.mjs && node tools/load-aos.mjs", "aos:spawn": "node tools/spawn-aos.mjs", "test": "yarn aos:build && node --test --test-concurrency 1 --experimental-wasm-memory64 **/*.test.mjs", - "prepare": "husky" + "prepare": "husky", + "install-lua-deps": "sh tools/install-lua-deps.sh" }, "devDependencies": { "@ardrive/turbo-sdk": "^1.19.0", diff --git a/src/common/balances.lua b/src/common/balances.lua index 1c4f69d..8b6ab95 100644 --- a/src/common/balances.lua +++ b/src/common/balances.lua @@ -2,7 +2,6 @@ -- @module balances local utils = require(".common.utils") -local json = require(".common.json") local balances = {} @@ -15,19 +14,19 @@ end --- Transfers the ANT to a specified wallet. ---@param to string - The wallet address to transfer the balance to. ----@return string - Returns the encoded JSON representation of the transferred balance. +---@return table function balances.transfer(to) utils.validateArweaveId(to) Balances = { [to] = 1 } --luacheck: ignore Owner Controllers Owner = to Controllers = {} - return json.encode({ [to] = 1 }) + return { [to] = 1 } end --- Retrieves the balance of a specified wallet. ---@param address string - The wallet address to retrieve the balance from. ----@return number - Returns the balance of the specified wallet. +---@return integer - Returns the balance of the specified wallet. function balances.balance(address) utils.validateArweaveId(address) local balance = Balances[address] or 0 @@ -35,55 +34,55 @@ function balances.balance(address) end --- Retrieves all balances. ----@return string - Returns the encoded JSON representation of all balances. +---@return table - Returns the encoded JSON representation of all balances. function balances.balances() - return json.encode(Balances) + return Balances end --- Sets the name of the ANT. ---@param name string - The name to set. ----@return string - Returns the encoded JSON representation of the updated name. +---@return table function balances.setName(name) assert(type(name) == "string", "Name must be a string") Name = name - return json.encode({ name = Name }) + return { name = Name } end --- Sets the ticker of the ANT. ---@param ticker string - The ticker to set. ----@return string - Returns the encoded JSON representation of the updated ticker. +---@return table function balances.setTicker(ticker) assert(type(ticker) == "string", "Ticker must be a string") Ticker = ticker - return json.encode({ ticker = Ticker }) + return { ticker = Ticker } end --- Sets the description of the ANT. ---@param description string - The description to set. ----@return string - Returns the encoded JSON representation of the updated description. +---@return table 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 }) + return { description = Description } end --- Sets the keywords of the ANT. ---@param keywords table - The keywords to set. ----@return string - Returns the encoded JSON representation of the updated keywords. +---@return table function balances.setKeywords(keywords) utils.validateKeywords(keywords) Keywords = keywords - return json.encode({ keywords = Keywords }) + return { keywords = Keywords } end --- Sets the logo of the ANT. ---@param logo string - The Arweave transaction ID that represents the logo. ----@return string - Returns the encoded JSON representation of the updated logo. +---@return table function balances.setLogo(logo) Logo = logo - return json.encode({ logo = Logo }) + return { logo = Logo } end return balances diff --git a/src/common/controllers.lua b/src/common/controllers.lua index a7bc84d..5cb0b2e 100644 --- a/src/common/controllers.lua +++ b/src/common/controllers.lua @@ -1,11 +1,10 @@ -local json = require(".common.json") local utils = require(".common.utils") local controllers = {} --- Set a controller. ---@param controller string The controller to set. ----@return string The encoded JSON representation of the updated controllers. +---@return table function controllers.setController(controller) utils.validateArweaveId(controller) @@ -14,12 +13,12 @@ function controllers.setController(controller) end table.insert(Controllers, controller) - return json.encode(Controllers) + return Controllers end --- Remove a controller. ---@param controller string The controller to remove. ----@return string The encoded JSON representation of the updated controllers. +---@return table function controllers.removeController(controller) utils.validateArweaveId(controller) local controllerExists = false @@ -33,13 +32,13 @@ function controllers.removeController(controller) end assert(controllerExists ~= nil, "Controller does not exist") - return json.encode(Controllers) + return Controllers end --- Get all controllers. ----@return string The encoded JSON representation of the controllers. +---@return table function controllers.getControllers() - return json.encode(Controllers) + return Controllers end return controllers diff --git a/src/common/records.lua b/src/common/records.lua index 9237e7c..1c9b81f 100644 --- a/src/common/records.lua +++ b/src/common/records.lua @@ -1,5 +1,4 @@ local utils = require(".common.utils") -local json = require(".common.json") local records = {} -- defaults to landing page txid Records = Records or { ["@"] = { transactionId = "-k7t8xMoB8hW482609Z9F4bTFMC3MnuW8bTvTyT8pFI", ttlSeconds = 3600 } } @@ -8,7 +7,7 @@ Records = Records or { ["@"] = { transactionId = "-k7t8xMoB8hW482609Z9F4bTFMC3Mn ---@param name string The name of the record. ---@param transactionId string The transaction ID of the record. ---@param ttlSeconds number The time-to-live in seconds for the record. ----@return string The encoded JSON representation of the record. +---@return Record function records.setRecord(name, transactionId, ttlSeconds) utils.validateUndername(name) utils.validateArweaveId(transactionId) @@ -25,35 +24,35 @@ function records.setRecord(name, transactionId, ttlSeconds) ttlSeconds = ttlSeconds, } - return json.encode({ + return { transactionId = transactionId, ttlSeconds = ttlSeconds, - }) + } end --- Remove a record from the ANT. ---@param name string The name of the record to remove. ----@return string The encoded JSON representation of the deletion message. +---@return table<'message', string> function records.removeRecord(name) utils.validateUndername(name) Records[name] = nil - return json.encode({ message = "Record deleted" }) + return { message = "Record deleted" } end --- Get a record from the ANT. ---@param name string The name of the record to retrieve. ----@return string The encoded JSON representation of the record. +---@return Record function records.getRecord(name) utils.validateUndername(name) assert(Records[name] ~= nil, "Record does not exist") - return json.encode(Records[name]) + return Records[name] end --- Get all records from the ANT ----@return string The encoded JSON representation of all records. +---@return table function records.getRecords() - return json.encode(Records) + return Records end return records From 467fb1c2ee2836af9a2cadbdd241551c9a020bef Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Tue, 12 Nov 2024 16:08:30 -0600 Subject: [PATCH 21/28] fix(spec): update apis to return according to spec --- src/common/balances.lua | 20 ++++++-------------- src/common/records.lua | 4 ++-- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/common/balances.lua b/src/common/balances.lua index 8b6ab95..97e4f01 100644 --- a/src/common/balances.lua +++ b/src/common/balances.lua @@ -5,23 +5,15 @@ local utils = require(".common.utils") local balances = {} ---- Checks if a wallet has sufficient balance. ----@param wallet string - The wallet address to check. ----@return boolean - Returns true if the wallet has a balance greater than 0, otherwise false. -function balances.walletHasSufficientBalance(wallet) - return Balances[wallet] ~= nil and Balances[wallet] > 0 -end - --- Transfers the ANT to a specified wallet. ---@param to string - The wallet address to transfer the balance to. ----@return table +---@return nil function balances.transfer(to) utils.validateArweaveId(to) Balances = { [to] = 1 } --luacheck: ignore Owner Controllers Owner = to Controllers = {} - return { [to] = 1 } end --- Retrieves the balance of a specified wallet. @@ -45,7 +37,7 @@ end function balances.setName(name) assert(type(name) == "string", "Name must be a string") Name = name - return { name = Name } + return { Name = Name } end --- Sets the ticker of the ANT. @@ -54,7 +46,7 @@ end function balances.setTicker(ticker) assert(type(ticker) == "string", "Ticker must be a string") Ticker = ticker - return { ticker = Ticker } + return { Ticker = Ticker } end --- Sets the description of the ANT. @@ -64,7 +56,7 @@ 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 { description = Description } + return { Description = Description } end --- Sets the keywords of the ANT. @@ -74,7 +66,7 @@ function balances.setKeywords(keywords) utils.validateKeywords(keywords) Keywords = keywords - return { keywords = Keywords } + return { Keywords = Keywords } end --- Sets the logo of the ANT. @@ -82,7 +74,7 @@ end ---@return table function balances.setLogo(logo) Logo = logo - return { logo = Logo } + return { Logo = Logo } end return balances diff --git a/src/common/records.lua b/src/common/records.lua index 1c9b81f..ae325de 100644 --- a/src/common/records.lua +++ b/src/common/records.lua @@ -32,11 +32,11 @@ end --- Remove a record from the ANT. ---@param name string The name of the record to remove. ----@return table<'message', string> +---@return table Returns the records of the ANT function records.removeRecord(name) utils.validateUndername(name) Records[name] = nil - return { message = "Record deleted" } + return Records end --- Get a record from the ANT. From b40e51e003c990984e1154d42f77b0e70c6ece4e Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Tue, 12 Nov 2024 16:13:43 -0600 Subject: [PATCH 22/28] fix(tests): add test for setLogo --- spec/ant_spec.lua | 13 +++++++++++++ src/common/balances.lua | 1 + src/common/main.lua | 1 - 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/spec/ant_spec.lua b/spec/ant_spec.lua index d2fb754..6d1f368 100644 --- a/spec/ant_spec.lua +++ b/spec/ant_spec.lua @@ -27,6 +27,7 @@ describe("Arweave Name Token", function() _G.Description = "ANT's description" _G.Keywords = { "KEYWORD-1", "KEYWORD-2", "KEYWORD-3" } _G.Denomination = 1 + _G.Logo = "" end) it("Initializes the state of the process", function() @@ -167,4 +168,16 @@ describe("Arweave Name Token", function() balances.setKeywords(notAnArray) end, "Keywords must be an array") end) + + it("should set the logo", function() + local logo = string.rep("1", 43) + balances.setLogo(logo) + assert.are.same(logo, _G.Logo) + end) + it("should not set the logo with invalid id", function() + local logo = string.rep("1", 42) + assert.has_error(function() + balances.setLogo(logo) + end) + end) end) diff --git a/src/common/balances.lua b/src/common/balances.lua index 97e4f01..5c5f730 100644 --- a/src/common/balances.lua +++ b/src/common/balances.lua @@ -73,6 +73,7 @@ end ---@param logo string - The Arweave transaction ID that represents the logo. ---@return table function balances.setLogo(logo) + utils.validateArweaveId(logo) Logo = logo return { Logo = Logo } end diff --git a/src/common/main.lua b/src/common/main.lua index 6ec0b60..c6bcb64 100644 --- a/src/common/main.lua +++ b/src/common/main.lua @@ -213,7 +213,6 @@ function ant.init() createActionHandler(ActionMap.SetLogo, function(msg) utils.assertHasPermission(msg.From) - utils.validateArweaveId(msg.Logo) return balances.setLogo(msg.Logo) end) From 34316ae44c426e93e4a7addd48652025a0750696 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Wed, 13 Nov 2024 07:23:14 -0600 Subject: [PATCH 23/28] fix(utils): remove unused utils --- src/common/controllers.lua | 6 +- src/common/main.lua | 2 +- src/common/types.lua | 2 +- src/common/utils.lua | 186 +------------------------------------ 4 files changed, 7 insertions(+), 189 deletions(-) diff --git a/src/common/controllers.lua b/src/common/controllers.lua index 5cb0b2e..d37759c 100644 --- a/src/common/controllers.lua +++ b/src/common/controllers.lua @@ -4,7 +4,7 @@ local controllers = {} --- Set a controller. ---@param controller string The controller to set. ----@return table +---@return string[] function controllers.setController(controller) utils.validateArweaveId(controller) @@ -18,7 +18,7 @@ end --- Remove a controller. ---@param controller string The controller to remove. ----@return table +---@return string[] function controllers.removeController(controller) utils.validateArweaveId(controller) local controllerExists = false @@ -36,7 +36,7 @@ function controllers.removeController(controller) end --- Get all controllers. ----@return table +---@return string[] function controllers.getControllers() return Controllers end diff --git a/src/common/main.lua b/src/common/main.lua index c6bcb64..a925805 100644 --- a/src/common/main.lua +++ b/src/common/main.lua @@ -36,7 +36,7 @@ function ant.init() ---@alias Description string ---@description A brief description of this ANT up to 255 characters Description = Description or "A brief description of this ANT." - ---@alias Keywords table + ---@alias Keywords string[] ---@description A list of keywords that describe this ANT. Each keyword must be a string, unique, and less than 32 characters. There can be up to 16 keywords Keywords = Keywords or {} ---@alias Denomination integer diff --git a/src/common/types.lua b/src/common/types.lua index 548d320..5ab943b 100644 --- a/src/common/types.lua +++ b/src/common/types.lua @@ -40,7 +40,7 @@ --- Logo: string, --- Balances: table, --- Owner: string, ---- Controllers: table, +--- Controllers: string[], --- Denomination: integer, --- TotalSupply: integer, --- Initialized: boolean, diff --git a/src/common/utils.lua b/src/common/utils.lua index 044e6dd..58c791f 100644 --- a/src/common/utils.lua +++ b/src/common/utils.lua @@ -3,172 +3,6 @@ local constants = require(".common.constants") local json = require(".common.json") local utils = { _version = "0.0.1" } -local function isArray(table) - if type(table) == "table" then - local maxIndex = 0 - for k, _ in pairs(table) do - if type(k) ~= "number" or k < 1 or math.floor(k) ~= k then - return false -- If there's a non-integer key, it's not an array - end - maxIndex = math.max(maxIndex, k) - end - -- If the highest numeric index is equal to the number of elements, it's an array - return maxIndex == #table - end - return false -end - ---- @param fn function ---- @param arity number -utils.curry = function(fn, arity) - assert(type(fn) == "function", "function is required as first argument") - arity = arity or debug.getinfo(fn, "u").nparams - if arity < 2 then - return fn - end - - return function(...) - local args = { ... } - - if #args >= arity then - return fn(table.unpack(args)) - else - return utils.curry(function(...) - return fn(table.unpack(args), ...) - end, arity - #args) - end - end -end - ---- Concat two Array Tables. ---- @param a table ---- @param b table -utils.concat = utils.curry(function(a, b) - assert(type(a) == "table", "first argument should be a table that is an array") - assert(type(b) == "table", "second argument should be a table that is an array") - assert(isArray(a), "first argument should be a table") - assert(isArray(b), "second argument should be a table") - - local result = {} - for i = 1, #a do - result[#result + 1] = a[i] - end - for i = 1, #b do - result[#result + 1] = b[i] - end - return result -end, 2) - ---- reduce applies a function to a table ---- @param fn function ---- @param initial any ---- @param t table -utils.reduce = utils.curry(function(fn, initial, t) - assert(type(fn) == "function", "first argument should be a function that accepts (result, value, key)") - assert(type(t) == "table" and isArray(t), "third argument should be a table that is an array") - local result = initial - for k, v in pairs(t) do - if result == nil then - result = v - else - result = fn(result, v, k) - end - end - return result -end, 3) - ---- @param fn function ---- @param data table -utils.map = utils.curry(function(fn, data) - assert(type(fn) == "function", "first argument should be a unary function") - assert(type(data) == "table" and isArray(data), "second argument should be an Array") - - local function map(result, v, k) - result[k] = fn(v, k) - return result - end - - return utils.reduce(map, {}, data) -end, 2) - ---- @param fn function ---- @param data table -utils.filter = utils.curry(function(fn, data) - assert(type(fn) == "function", "first argument should be a unary function") - assert(type(data) == "table" and isArray(data), "second argument should be an Array") - - local function filter(result, v) - if fn(v) then - table.insert(result, v) - end - return result - end - - return utils.reduce(filter, {}, data) -end, 2) - ---- @param fn function ---- @param t table -utils.find = utils.curry(function(fn, t) - assert(type(fn) == "function", "first argument should be a unary function") - assert(type(t) == "table", "second argument should be a table that is an array") - for _, v in pairs(t) do - if fn(v) then - return v - end - end -end, 2) - ---- @param propName string ---- @param value string ---- @param object table -utils.propEq = utils.curry(function(propName, value, object) - assert(type(propName) == "string", "first argument should be a string") - -- assert(type(value) == "string", "second argument should be a string") - assert(type(object) == "table", "third argument should be a table") - - return object[propName] == value -end, 3) - ---- @param data table -utils.reverse = function(data) - assert(type(data) == "table", "argument needs to be a table that is an array") - return utils.reduce(function(result, v, i) - result[#data - i + 1] = v - return result - end, {}, data) -end - ---- @param ... function -utils.compose = utils.curry(function(...) - local mutations = utils.reverse({ ... }) - - return function(v) - local result = v - ---@diagnostic disable-next-line - for _, fn in pairs(mutations) do - assert(type(fn) == "function", "each argument needs to be a function") - result = fn(result) - end - return result - end -end, 2) - ---- @param propName string ---- @param object table -utils.prop = utils.curry(function(propName, object) - return object[propName] -end, 2) - ---- @param val any ---- @param t table -utils.includes = utils.curry(function(val, t) - assert(type(t) == "table", "argument needs to be a table") - return utils.find(function(v) - return v == val - end, t) ~= nil -end, 2) - --- @param t table utils.keys = function(t) assert(type(t) == "table", "argument needs to be a table") @@ -179,26 +13,10 @@ utils.keys = function(t) return keys end ---- @param t table -utils.values = function(t) - assert(type(t) == "table", "argument needs to be a table") - local values = {} - for _, value in pairs(t) do - table.insert(values, value) - end - return values -end - function utils.hasMatchingTag(tag, value) return Handlers.utils.hasMatchingTag(tag, value) end ----@param msg AoMessage -function utils.reply(msg) - ---@diagnostic disable-next-line - Handlers.utils.reply(msg) -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 @@ -399,7 +217,7 @@ end --- @param handlers Handlers --- @description Get the names of all handlers ---- @return table +--- @return string[] function utils.getHandlerNames(handlers) local names = {} for _, handler in ipairs(handlers.list) do @@ -485,7 +303,7 @@ function utils.createActionHandler(action, msgHandler, position) return utils.createHandler("Action", action, msgHandler, position) end ----@param keywords table +---@param keywords string[] ---@description Validates the keywords ---Amount of keywords must be less than or equal to 16 ---Each keyword must be a unique string of 32 characters or less From abad18f533d8b15693814d61f948dabcc1b9f240 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Wed, 13 Nov 2024 07:31:21 -0600 Subject: [PATCH 24/28] fix(notices): move notification methods to notices file --- src/common/main.lua | 7 +++-- src/common/notices.lua | 65 ++++++++++++++++++++++++++++++++++++++ src/common/utils.lua | 71 +++--------------------------------------- 3 files changed, 73 insertions(+), 70 deletions(-) create mode 100644 src/common/notices.lua diff --git a/src/common/main.lua b/src/common/main.lua index a925805..b3271f5 100644 --- a/src/common/main.lua +++ b/src/common/main.lua @@ -5,6 +5,7 @@ function ant.init() -- utils local json = require(".common.json") local utils = require(".common.utils") + local notices = require(".common.notices") local camel = utils.camelCase local createActionHandler = utils.createActionHandler @@ -96,8 +97,8 @@ function ant.init() utils.validateOwner(msg.From) balances.transfer(recipient) if not msg.Cast then - ao.send(utils.notices.debit(msg)) - ao.send(utils.notices.credit(msg)) + ao.send(notices.debit(msg)) + ao.send(notices.credit(msg)) end end) @@ -221,7 +222,7 @@ function ant.init() end) createActionHandler(ActionMap.State, function(msg) - utils.notices.notifyState(msg, msg.From) + notices.notifyState(msg, msg.From) end) -- IO Network Contract Handlers diff --git a/src/common/notices.lua b/src/common/notices.lua new file mode 100644 index 0000000..d6ce298 --- /dev/null +++ b/src/common/notices.lua @@ -0,0 +1,65 @@ +local notices = {} + +--- @param oldMsg AoMessage +--- @param newMsg AoMessage +--- @description Add forwarded tags to the new message +--- @return AoMessage +function notices.addForwardedTags(oldMsg, newMsg) + for tagName, tagValue in pairs(oldMsg) do + -- Tags beginning with "X-" are forwarded + if string.sub(tagName, 1, 2) == "X-" then + newMsg[tagName] = tagValue + end + end + return newMsg +end + +--- @param msg AoMessage +--- @description Create a credit notice message +--- @return AoMessage +function notices.credit(msg) + return notices.addForwardedTags(msg, { + Target = msg.Recipient, + Action = "Credit-Notice", + Sender = msg.From, + Quantity = tostring(1), + }) +end + +--- @param msg AoMessage +--- @description Create a debit notice message +--- @return AoMessage +function notices.debit(msg) + return notices.addForwardedTags(msg, { + Target = msg.From, + Action = "Debit-Notice", + Recipient = msg.Recipient, + Quantity = tostring(1), + }) +end + +--- @param notices table +function notices.sendNotices(notices) + for _, notice in ipairs(notices) do + ao.send(notice) + end +end + +--- @param msg AoMessage +--- @param target string +--- @description Notify the target of the current state +--- @return nil +function notices.notifyState(msg, target) + if not target then + print("No target specified for state notice") + return + end + + ao.send(notices.addForwardedTags(msg, { + Target = target, + Action = "State-Notice", + Data = json.encode(utils.getState()), + })) +end + +return notices diff --git a/src/common/utils.lua b/src/common/utils.lua index 58c791f..7bc1211 100644 --- a/src/common/utils.lua +++ b/src/common/utils.lua @@ -1,6 +1,7 @@ -- the majority of this file came from https://github.com/permaweb/aos/blob/main/process/utils.lua local constants = require(".common.constants") local json = require(".common.json") +local notices = require(".common.notices") local utils = { _version = "0.0.1" } --- @param t table @@ -151,70 +152,6 @@ function utils.getState() } end -utils.notices = {} - ---- @param oldMsg AoMessage ---- @param newMsg AoMessage ---- @description Add forwarded tags to the new message ---- @return AoMessage -function utils.notices.addForwardedTags(oldMsg, newMsg) - for tagName, tagValue in pairs(oldMsg) do - -- Tags beginning with "X-" are forwarded - if string.sub(tagName, 1, 2) == "X-" then - newMsg[tagName] = tagValue - end - end - return newMsg -end - ---- @param msg AoMessage ---- @description Create a credit notice message ---- @return AoMessage -function utils.notices.credit(msg) - return utils.notices.addForwardedTags(msg, { - Target = msg.Recipient, - Action = "Credit-Notice", - Sender = msg.From, - Quantity = tostring(1), - }) -end - ---- @param msg AoMessage ---- @description Create a debit notice message ---- @return AoMessage -function utils.notices.debit(msg) - return utils.notices.addForwardedTags(msg, { - Target = msg.From, - Action = "Debit-Notice", - Recipient = msg.Recipient, - Quantity = tostring(1), - }) -end - ---- @param notices table -function utils.notices.sendNotices(notices) - for _, notice in ipairs(notices) do - ao.send(notice) - end -end - ---- @param msg AoMessage ---- @param target string ---- @description Notify the target of the current state ---- @return nil -function utils.notices.notifyState(msg, target) - if not target then - print("No target specified for state notice") - return - end - - ao.send(utils.notices.addForwardedTags(msg, { - Target = target, - Action = "State-Notice", - Data = json.encode(utils.getState()), - })) -end - --- @param handlers Handlers --- @description Get the names of all handlers --- @return string[] @@ -270,7 +207,7 @@ function utils.createHandler(tagName, tagValue, handler, position) end, utils.errorHandler) if not handlerStatus then - ao.send(utils.notices.addForwardedTags(msg, { + ao.send(notices.addForwardedTags(msg, { Target = msg.From, Action = "Invalid-" .. tagValue .. "-Notice", Error = tagValue .. "-Error", @@ -278,7 +215,7 @@ function utils.createHandler(tagName, tagValue, handler, position) Data = handlerRes, })) elseif handlerRes then - ao.send(utils.notices.addForwardedTags(msg, { + ao.send(notices.addForwardedTags(msg, { Target = msg.From, Action = tagValue .. "-Notice", Data = type(handlerRes) == "string" and handlerRes or json.encode(handlerRes), @@ -288,7 +225,7 @@ function utils.createHandler(tagName, tagValue, handler, position) local hasNewOwner = Owner ~= prevOwner local hasDifferentControllers = #utils.keys(Controllers) ~= #utils.keys(prevControllers) if (hasNewOwner or hasDifferentControllers) and tagValue ~= "State" then - utils.notices.notifyState(msg, msg.From) + notices.notifyState(msg, msg.From) end return handlerRes From 81da676a2e3ac2bbdd7bb9e24fc774aae8c39e6b Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Wed, 13 Nov 2024 07:32:26 -0600 Subject: [PATCH 25/28] fix(scripts): remove script from package json --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 73dd92a..43d5f1e 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,7 @@ "aos:load": "node tools/bundle-aos.mjs && node tools/load-aos.mjs", "aos:spawn": "node tools/spawn-aos.mjs", "test": "yarn aos:build && node --test --test-concurrency 1 --experimental-wasm-memory64 **/*.test.mjs", - "prepare": "husky", - "install-lua-deps": "sh tools/install-lua-deps.sh" + "prepare": "husky" }, "devDependencies": { "@ardrive/turbo-sdk": "^1.19.0", From ec0d7566e03836a7823dc1da34c28439722455ce Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Wed, 13 Nov 2024 07:34:43 -0600 Subject: [PATCH 26/28] fix(lint): fix linting on notices --- src/common/notices.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/notices.lua b/src/common/notices.lua index d6ce298..1c24c70 100644 --- a/src/common/notices.lua +++ b/src/common/notices.lua @@ -1,3 +1,5 @@ +local json = require("json") +local utils = require(".common.utils") local notices = {} --- @param oldMsg AoMessage From 9d514a31d5b973b5918b287e64ae8da2909ce93e Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Wed, 13 Nov 2024 07:37:59 -0600 Subject: [PATCH 27/28] fix(transfer): update transfer method to return the changed balances of the ant --- src/common/balances.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/balances.lua b/src/common/balances.lua index 5c5f730..256211d 100644 --- a/src/common/balances.lua +++ b/src/common/balances.lua @@ -14,6 +14,8 @@ function balances.transfer(to) --luacheck: ignore Owner Controllers Owner = to Controllers = {} + + return { [to] = 1 } end --- Retrieves the balance of a specified wallet. From e9c857133f4825bb4954cddd2f1ceb9ef7c433e6 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Wed, 13 Nov 2024 07:47:54 -0600 Subject: [PATCH 28/28] fix(circular import): remove utils from notices file since it causes a circular reference --- src/common/balances.lua | 2 +- src/common/notices.lua | 26 +++++++++++++++++++++----- src/common/types.lua | 2 +- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/common/balances.lua b/src/common/balances.lua index 256211d..6c7fad7 100644 --- a/src/common/balances.lua +++ b/src/common/balances.lua @@ -7,7 +7,7 @@ local balances = {} --- Transfers the ANT to a specified wallet. ---@param to string - The wallet address to transfer the balance to. ----@return nil +---@return table function balances.transfer(to) utils.validateArweaveId(to) Balances = { [to] = 1 } diff --git a/src/common/notices.lua b/src/common/notices.lua index 1c24c70..189a39d 100644 --- a/src/common/notices.lua +++ b/src/common/notices.lua @@ -1,5 +1,4 @@ local json = require("json") -local utils = require(".common.utils") local notices = {} --- @param oldMsg AoMessage @@ -40,9 +39,9 @@ function notices.debit(msg) }) end ---- @param notices table -function notices.sendNotices(notices) - for _, notice in ipairs(notices) do +--- @param noticesToSend table +function notices.sendNotices(noticesToSend) + for _, notice in ipairs(noticesToSend) do ao.send(notice) end end @@ -57,10 +56,27 @@ function notices.notifyState(msg, target) return end + ---@type AntState + local state = { + Records = Records, + Controllers = Controllers, + Balances = Balances, + Owner = Owner, + Name = Name, + Ticker = Ticker, + Logo = Logo, + Description = Description, + Keywords = Keywords, + Denomination = Denomination, + TotalSupply = TotalSupply, + Initialized = Initialized, + ["Source-Code-TX-ID"] = SourceCodeTxId, + } + ao.send(notices.addForwardedTags(msg, { Target = target, Action = "State-Notice", - Data = json.encode(utils.getState()), + Data = json.encode(state), })) end diff --git a/src/common/types.lua b/src/common/types.lua index 5ab943b..7dd6845 100644 --- a/src/common/types.lua +++ b/src/common/types.lua @@ -36,7 +36,7 @@ --- Name: string, --- Ticker: string, --- Description: string, ---- Keywords: string, +--- Keywords: table, --- Logo: string, --- Balances: table, --- Owner: string,