From d769d1da231124fff6c88dc3569c6ab24b77ad99 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 10 May 2024 09:43:15 -0400 Subject: [PATCH 1/6] fix(arns): change arns to throw errors and wrap calls with pcall --- contract/src/ao.lua | 201 ++++++++++++++++++++++++++++++++++++++++++ contract/src/arns.lua | 25 ++---- contract/src/main.lua | 38 +++++--- 3 files changed, 237 insertions(+), 27 deletions(-) create mode 100644 contract/src/ao.lua diff --git a/contract/src/ao.lua b/contract/src/ao.lua new file mode 100644 index 00000000..76dc8025 --- /dev/null +++ b/contract/src/ao.lua @@ -0,0 +1,201 @@ +local ao = { + _version = "0.0.5", + id = "", + _module = "", + authorities = {}, + _ref = 0, + outbox = {Output = {}, Messages = {}, Spawns = {}, Assignments = {}} +} + +local function _includes(list) + return function(key) + local exists = false + for _, listKey in ipairs(list) do + if key == listKey then + exists = true + break + end + end + if not exists then return false end + return true + end +end + +local function isArray(table) + if type(table) == "table" then + local maxIndex = 0 + for k, v 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 + +local function padZero32(num) return string.format("%032d", num) end + +function ao.normalize(msg) + for _, o in ipairs(msg.Tags) do + if not _includes({ + 'Data-Protocol', 'Variant', 'From-Process', 'From-Module', 'Type', + 'Ref_', 'From', 'Owner', 'Anchor', 'Target', 'Data', 'Tags' + })(o.name) then msg[o.name] = o.value end + end + return msg +end + +function ao.init(env) + if ao.id == "" then ao.id = env.Process.Id end + + if ao._module == "" then + for _, o in ipairs(env.Process.Tags) do + if o.name == "Module" then ao._module = o.value end + end + end + + if #ao.authorities < 1 then + for _, o in ipairs(env.Process.Tags) do + if o.name == "Authority" then + table.insert(ao.authorities, o.value) + end + end + end + + ao.outbox = {Output = {}, Messages = {}, Spawns = {}, Assignments = {}} + ao.env = env + +end + +function ao.log(txt) + if type(ao.outbox.Output) == 'string' then + ao.outbox.Output = {ao.outbox.Output} + end + table.insert(ao.outbox.Output, txt) +end + +-- clears outbox +function ao.clearOutbox() ao.outbox = {Output = {}, Messages = {}, Spawns = {}, Assignments = {}} end + +function ao.send(msg) + assert(type(msg) == 'table', 'msg should be a table') + ao._ref = ao._ref + 1 + + local message = { + Target = msg.Target, + Data = msg.Data, + Anchor = padZero32(ao._ref), + Tags = { + {name = "Data-Protocol", value = "ao"}, + {name = "Variant", value = "ao.TN.1"}, + {name = "Type", value = "Message"}, + {name = "From-Process", value = ao.id}, + {name = "From-Module", value = ao._module}, + {name = "Ref_", value = tostring(ao._ref)} + } + } + + -- if custom tags in root move them to tags + for k, v in pairs(msg) do + if not _includes({"Target", "Data", "Anchor", "Tags", "From"})(k) then + table.insert(message.Tags, {name = k, value = v}) + end + end + + if msg.Tags then + if isArray(msg.Tags) then + for _, o in ipairs(msg.Tags) do + table.insert(message.Tags, o) + end + else + for k, v in pairs(msg.Tags) do + table.insert(message.Tags, {name = k, value = v}) + end + end + end + + -- add message to outbox + table.insert(ao.outbox.Messages, message) + + return message +end + +function ao.spawn(module, msg) + assert(type(module) == "string", "module source id is required!") + assert(type(msg) == 'table', 'msg should be a table') + -- inc spawn reference + ao._ref = ao._ref + 1 + + local spawn = { + Data = msg.Data or "NODATA", + Anchor = padZero32(ao._ref), + Tags = { + {name = "Data-Protocol", value = "ao"}, + {name = "Variant", value = "ao.TN.1"}, + {name = "Type", value = "Process"}, + {name = "From-Process", value = ao.id}, + {name = "From-Module", value = ao._module}, + {name = "Module", value = module}, + {name = "Ref_", value = tostring(ao._ref)} + } + } + + -- if custom tags in root move them to tags + for k, v in pairs(msg) do + if not _includes({"Target", "Data", "Anchor", "Tags", "From"})(k) then + table.insert(spawn.Tags, {name = k, value = v}) + end + end + + if msg.Tags then + if isArray(msg.Tags) then + for _, o in ipairs(msg.Tags) do + table.insert(spawn.Tags, o) + end + else + for k, v in pairs(msg.Tags) do + table.insert(spawn.Tags, {name = k, value = v}) + end + end + end + + -- add spawn to outbox + table.insert(ao.outbox.Spawns, spawn) + + return spawn +end + +function ao.assign(assignment) + assert(type(assignment) == 'table', 'assignment should be a table') + assert(type(assignment.Processes) == 'table', 'Processes should be a table') + assert(type(assignment.Message) == "string", "Message should be a string") + table.insert(ao.outbox.Assignments, assignment) +end + +function ao.isTrusted(msg) + if #ao.authorities == 0 then return true end + + for _, authority in ipairs(ao.authorities) do + if msg.From == authority then return true end + if msg.Owner == authority then return true end + end + return false +end + +function ao.result(result) + -- if error then only send the Error to CU + if ao.outbox.Error or result.Error then + return {Error = result.Error or ao.outbox.Error} + end + return { + Output = result.Output or ao.outbox.Output, + Messages = ao.outbox.Messages, + Spawns = ao.outbox.Spawns, + Assignments = ao.outbox.Assignments + } +end + +return ao diff --git a/contract/src/arns.lua b/contract/src/arns.lua index d7e43f55..a7f117f5 100644 --- a/contract/src/arns.lua +++ b/contract/src/arns.lua @@ -1,20 +1,13 @@ -- arns.lua -require("state") local utils = require("utils") local constants = require("constants") local arns = {} -if not Balances then - Balances = {} -end - -if not Records then - Records = {} -end +Balances = Balances or {} +Records = Records or {} +Auctions = Auctions or {} +Reserved = Reserved or {} -if not Auctions then - Auctions = {} -end if not Reserved then Reserved = {} @@ -71,15 +64,15 @@ function arns.buyRecord(name, purchaseType, years, from, auction, timestamp, pro local totalRegistrationFee = utils.calculateRegistrationFee(purchaseType, name, years) if not Balances[from] or Balances[from] < totalRegistrationFee then - return false, "Insufficient balance" + error("Insufficient balance") end if Auctions[name] then - return false, "Name is in auction" + error("Name is in auction") end if Records[name] then - return false, "Name already exists" + error("Name is already registered") end -- Transfer tokens to the protocol balance @@ -122,7 +115,7 @@ function arns.extendLease(from, name, years, timestamp) local totalExtensionFee = utils.calculateExtensionFee(name, years, record.type) if not utils.walletHasSufficientBalance(from, totalExtensionFee) then - return false, "Insufficient balance" + error("Insufficient balance") end -- Transfer tokens to the protocol balance @@ -162,7 +155,7 @@ function arns.increaseUndernameCount(from, name, qty, timestamp) local additionalUndernameCost = utils.calculateUndernameCost(name, qty, record.type, yearsRemaining) if not utils.walletHasSufficientBalance(from, additionalUndernameCost) then - return false, "Insufficient balance" + error("Insufficient balance") end -- Transfer tokens to the protocol balance diff --git a/contract/src/main.lua b/contract/src/main.lua index fede6751..3f9e736a 100644 --- a/contract/src/main.lua +++ b/contract/src/main.lua @@ -3,6 +3,7 @@ local process = { _version = "0.0.1" } require("state") local token = require("token") +local ao = require("ao") local arns = require("arns") local gar = require("gar") local utils = require("utils") @@ -180,7 +181,7 @@ Handlers.add(ActionMap.IncreaseVault, utils.hasMatchingTag("Action", ActionMap.I end) Handlers.add(ActionMap.BuyRecord, utils.hasMatchingTag("Action", ActionMap.BuyRecord), function(msg) - local result, err = arns.buyRecord( + local success, result = pcall(arns.buyRecord, msg.Tags.Name, msg.Tags.PurchaseType, msg.Tags.Years, @@ -189,7 +190,7 @@ Handlers.add(ActionMap.BuyRecord, utils.hasMatchingTag("Action", ActionMap.BuyRe msg.Timestamp, msg.Tags.ProcessId ) - if err then + if not success then ao.send({ Target = msg.From, Tags = { @@ -197,28 +198,43 @@ Handlers.add(ActionMap.BuyRecord, utils.hasMatchingTag("Action", ActionMap.BuyRe Name = tostring(msg.Tags.Name), ProcessId = tostring(msg.Tags.ProcessId), }, - Data = tostring(err), + Data = tostring(result), }) + return else ao.send({ Target = msg.From, Tags = { Action = "ArNS-Purchase-Notice", Sender = msg.From }, + Error = tostring(result), Data = tostring(json.encode(result)), }) end end) Handlers.add(ActionMap.SubmitAuctionBid, utils.hasMatchingTag("Action", ActionMap.SubmitAuctionBid), function(msg) - arns.submitAuctionBid(msg) + local status, result = pcall(arns.submitAuctionBid, msg.From, msg.Tags.Name, msg.Tags.Bid, msg.Timestamp) + if not status then + ao.send({ + Target = msg.From, + Tags = { Action = 'Invalid-Auction-Bid' }, + Data = tostring(result) + }) + else + ao.send({ + Target = msg.From, + Tags = { Action = 'Auction-Bid-Submitted' }, + Data = tostring(json.encode(result)) + }) + end end) Handlers.add(ActionMap.ExtendLease, utils.hasMatchingTag("Action", ActionMap.ExtendLease), function(msg) - local result, err = arns.extendLease(msg.From, msg.Tags.Name, msg.Tags.Years, msg.Timestamp) - if err then + local success, result = pcall(arns.extendLease, msg.From, msg.Tags.Name, msg.Tags.Years, msg.Timestamp) + if not success then ao.send({ Target = msg.From, Tags = { Action = 'Invalid-Extend-Lease' }, - Data = tostring(err) + Data = tostring(result) }) else ao.send({ @@ -232,12 +248,12 @@ end) Handlers.add(ActionMap.IncreaseUndernameCount, utils.hasMatchingTag("Action", ActionMap.IncreaseUndernameCount), function(msg) - local result, err = arns.increaseUndernameCount(msg.From, msg.Tags.Name, msg.Tags.Quantity, msg.Timestamp) - if err then + local status, result = pcall(arns.increaseUndernameCount, msg.From, msg.Tags.Name, msg.Tags.Quantity, msg.Timestamp) + if not status then ao.send({ Target = msg.From, Tags = { Action = 'Invalid-Undername-Increase' }, - Data = tostring(err) + Data = tostring(result) }) else ao.send({ @@ -418,7 +434,7 @@ end) -- handler showing how we can fetch data from classes in lua Handlers.add(ActionMap.DemandFactor, utils.hasMatchingTag("Action", ActionMap.DemandFactor), function(msg) -- wrap in a protected call, and return the result or error accoringly to sender - local status, result = pcall(function() return Demand:getDemandFactor() end) + local status, result = pcall(Demand.getDemandFactor) if status then ao.send({ Target = msg.From, Data = tostring(result) }) else From 71fb971c675b89820605423604214b70dcee9641 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 10 May 2024 10:11:23 -0400 Subject: [PATCH 2/6] chore(test): update test to call using pcall and check errors --- contract/spec/arns_spec.lua | 104 +++++++++++++++++++++++++++--------- contract/src/arns.lua | 55 +++++++++---------- 2 files changed, 106 insertions(+), 53 deletions(-) diff --git a/contract/spec/arns_spec.lua b/contract/spec/arns_spec.lua index 7bba4d0d..d82d98f3 100644 --- a/contract/spec/arns_spec.lua +++ b/contract/spec/arns_spec.lua @@ -3,28 +3,20 @@ local arns = require("arns") local constants = require("constants") describe("arns", function() - local original_clock = os.clock local timestamp = os.clock() - setup(function() end) - - teardown(function() end) - - it("adds a record to the global balance object", function() - Balances["Bob"] = 5000000 - local result = arns.buyRecord("test-name", "lease", 1, "Bob", false, timestamp, testProcessId) - assert.are.same({ - purchasePrice = 1500, - type = "lease", - undernameCount = 10, - processId = testProcessId, - startTimestamp = 0, - endTimestamp = timestamp + constants.MS_IN_A_YEAR * 1, - }, result) + -- stub out the global state for these tests + before_each(function() + _G.Records = {} + _G.Balances = { + ["Bob"] = 5000000, + } end) - it("should allow you to lease a record", function() - local result = arns.buyRecord("test-name-2", "lease", 1, "Bob", false, timestamp, testProcessId) + it("adds validate a lease request and add it to global records object", function() + local status, result = pcall(arns.buyRecord, "test-name", "lease", 1, "Bob", false, timestamp, testProcessId) + print(result) + assert.is_true(status) assert.are.same({ purchasePrice = 1500, type = "lease", @@ -33,11 +25,25 @@ describe("arns", function() startTimestamp = 0, endTimestamp = timestamp + constants.MS_IN_A_YEAR * 1, }, result) + assert.are.same({ + ["test-name"] = { + purchasePrice = 1500, + type = "lease", + undernameCount = 10, + processId = testProcessId, + startTimestamp = 0, + endTimestamp = timestamp + constants.MS_IN_A_YEAR * 1, + }, + }, Records) + assert.are.same({ + ["Bob"] = 4998500, + [_G.ao.id] = 1500, + }, Balances) end) - it("should allow you to permabuy a record", function() - Balances["Bob"] = 5000000 - local result, err = arns.buyRecord("test-permabuy-name", "permabuy", 1, "Bob", false, timestamp, testProcessId) + it("should validate a permabuy request and add the record to global state and deduct balance from caller", function() + local status, result = pcall(arns.buyRecord, "test-permabuy", "permabuy", 1, "Bob", false, timestamp, testProcessId) + assert.is_true(status) assert.are.same({ purchasePrice = 3000, type = "permabuy", @@ -46,10 +52,45 @@ describe("arns", function() startTimestamp = 0, endTimestamp = nil, }, result) + assert.are.same({ + ["test-permabuy"] = { + purchasePrice = 3000, + type = "permabuy", + undernameCount = 10, + processId = testProcessId, + startTimestamp = 0, + endTimestamp = nil, + }, + }, Records) + assert.are.same({ + ["Bob"] = 4997000, + [_G.ao.id] = 3000, + }, Balances) + end) + + it('should throw an error if the record already exists', function() + Records["test-name"] = { + endTimestamp = timestamp + constants.MS_IN_A_YEAR, + processId = testProcessId, + purchasePrice = 1500, + startTimestamp = 0, + type = "lease", + undernameCount = 10, + } + local status, result = pcall(arns.buyRecord, "test-name", "lease", 1, "Bob", false, timestamp, testProcessId) + assert.is_false(status) + assert.match("Name is already registered", result) end) - it("should allow you to increase the undername count", function() - Records["test-name-4"] = { + it('should throw an error if the user does not have enough funds', function() + Balances["Bob"] = 0 + local status, result = pcall(arns.buyRecord, "test-name", "lease", 1, "Bob", false, timestamp, testProcessId) + assert.is_false(status) + assert.match("Insufficient funds", result) + end) + + it("should increase the undername count and properly deduct balance", function() + Records["test-name"] = { endTimestamp = timestamp + constants.MS_IN_A_YEAR, processId = testProcessId, purchasePrice = 1500, @@ -57,7 +98,8 @@ describe("arns", function() type = "lease", undernameCount = 10, } - local result, err = arns.increaseUndernameCount("Bob", "test-name-4", 50, timestamp) + local status, result = pcall(arns.increaseUndernameCount, "Bob", "test-name", 50, timestamp) + assert.is_true(status) assert.are.same({ endTimestamp = timestamp + constants.MS_IN_A_YEAR, processId = testProcessId, @@ -66,5 +108,19 @@ describe("arns", function() type = "lease", undernameCount = 60, }, result) + assert.are.same({ + ["test-name"] = { + endTimestamp = timestamp + constants.MS_IN_A_YEAR, + processId = testProcessId, + purchasePrice = 1500, + startTimestamp = 0, + type = "lease", + undernameCount = 60, + }, + }, Records) + assert.are.same({ + ["Bob"] = 4999937.5, + [_G.ao.id] = 62.5, + }, Balances) end) end) diff --git a/contract/src/arns.lua b/contract/src/arns.lua index a7f117f5..ff3851d2 100644 --- a/contract/src/arns.lua +++ b/contract/src/arns.lua @@ -9,34 +9,6 @@ Auctions = Auctions or {} Reserved = Reserved or {} -if not Reserved then - Reserved = {} - Reserved["gateway"] = { - endTimestamp = 1725080400000, - target = "QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ", - } - - Reserved["help"] = { - endTimestamp = 1725080400000, - target = "QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ", - } - - Reserved["io"] = { - endTimestamp = 1725080400000, - target = "QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ", - } - - Reserved["nodes"] = { - endTimestamp = 1725080400000, - target = "QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ", - } - - Reserved["www"] = { - endTimestamp = 1725080400000, - target = "QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ", - } -end - -- Needs auctions -- Needs demand factor @@ -64,7 +36,7 @@ function arns.buyRecord(name, purchaseType, years, from, auction, timestamp, pro local totalRegistrationFee = utils.calculateRegistrationFee(purchaseType, name, years) if not Balances[from] or Balances[from] < totalRegistrationFee then - error("Insufficient balance") + error("Insufficient funds") end if Auctions[name] then @@ -189,6 +161,10 @@ function arns.getAuction(name) return Auctions[name] end +function arns.getAuctions() + return Auctions +end + function arns.getReservedName(name) if Reserved[name] == nil then return nil @@ -196,4 +172,25 @@ function arns.getReservedName(name) return Reserved[name] end +function arns.addReservedName(name, details) + if Reserved[name] then + error("Name is already reserved") + end + + if Records[name] then + error("Name is already registered") + end + + if Auctions[name] then + error("Name is in auction") + end + + Reserved[name] = details + return Reserved[name] +end + +function arns.getReservedNames() + return Reserved +end + return arns From 5a130d179c50d4a05cc9bfda096d7280a89bc5d1 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 10 May 2024 10:12:03 -0400 Subject: [PATCH 3/6] chore(test): cleanup --- contract/spec/arns_spec.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/contract/spec/arns_spec.lua b/contract/spec/arns_spec.lua index d82d98f3..0c5a7116 100644 --- a/contract/spec/arns_spec.lua +++ b/contract/spec/arns_spec.lua @@ -15,7 +15,6 @@ describe("arns", function() it("adds validate a lease request and add it to global records object", function() local status, result = pcall(arns.buyRecord, "test-name", "lease", 1, "Bob", false, timestamp, testProcessId) - print(result) assert.is_true(status) assert.are.same({ purchasePrice = 1500, From 4ebf86ed1f52527e572eacfd1fe0d9134afd6b27 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 10 May 2024 10:16:13 -0400 Subject: [PATCH 4/6] chore: remove html reporter --- .luacov | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.luacov b/.luacov index 4199cba3..b8e8ad16 100644 --- a/.luacov +++ b/.luacov @@ -2,10 +2,6 @@ return { statsfile = "coverage/luacov.stats.out", reportfile = "coverage/coverage_report.out", runreport = true, - reporter = "html", - html = { - outputDirectory = 'coverage' - }, deleteStatsFile = true, include = {"contract"}, exclude = {"contract/spec"} From 58dd0b3f6a14847747f6f2a22bdd689a8e41b24d Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 10 May 2024 10:24:01 -0400 Subject: [PATCH 5/6] chore: update codecov configuration --- .luacov | 2 +- contract/spec/setup.lua | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.luacov b/.luacov index b8e8ad16..a7e08cbc 100644 --- a/.luacov +++ b/.luacov @@ -4,5 +4,5 @@ return { runreport = true, deleteStatsFile = true, include = {"contract"}, - exclude = {"contract/spec"} + exclude = {"contract/spec", "crypto"} } diff --git a/contract/spec/setup.lua b/contract/spec/setup.lua index 77de8ac5..1061b1e3 100644 --- a/contract/spec/setup.lua +++ b/contract/spec/setup.lua @@ -1,9 +1,4 @@ package.path = "./contract/src/?.lua;" .. package.path -require("luacov") -require("state") -require('token') -require('demand') -require('constants') _G.ao = { send = function() From 73d7602d45bd685171808aef9301b18535bebdb2 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 10 May 2024 10:26:58 -0400 Subject: [PATCH 6/6] chore: remove codecoverage file paths --- .github/workflows/build.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 42a43caf..657a5bb8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,7 +28,6 @@ jobs: - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4.0.1 with: - files: ./coverage/coverage_report.out,./coverage/luacov.stats.out token: ${{ secrets.CODECOV_TOKEN }} deploy: