Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(arns): change arns to throw errors and wrap calls with pcall #16

Merged
merged 6 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ jobs:
- name: Upload coverage reports to Codecov
uses: codecov/[email protected]
with:
files: ./coverage/coverage_report.out,./coverage/luacov.stats.out
token: ${{ secrets.CODECOV_TOKEN }}

deploy:
Expand Down
6 changes: 1 addition & 5 deletions .luacov
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ 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"}
exclude = {"contract/spec", "crypto"}
}
103 changes: 79 additions & 24 deletions contract/spec/arns_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,19 @@ 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)
assert.is_true(status)
assert.are.same({
purchasePrice = 1500,
type = "lease",
Expand All @@ -33,11 +24,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",
Expand All @@ -46,18 +51,54 @@ 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,
startTimestamp = 0,
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,
Expand All @@ -66,5 +107,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)
5 changes: 0 additions & 5 deletions contract/spec/setup.lua
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
201 changes: 201 additions & 0 deletions contract/src/ao.lua
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading