From 693575ba767adac2de947909e0efe02a2ad6215c Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 12 Oct 2024 11:50:27 -0500 Subject: [PATCH 01/41] fix(main): comment out ticked reward distribution causing error --- src/main.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.lua b/src/main.lua index 09a3814..aebbc5b 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1423,7 +1423,7 @@ addEventingHandler("distribute", utils.hasMatchingTag("Action", "Tick"), functio assert(msg.Timestamp, "Timestamp is required for a tick interaction") local msgTimestamp = tonumber(msg.Timestamp) -- tick and distribute rewards for every index between the last ticked epoch and the current epoch - local tickedRewardDistributions = {} + -- local tickedRewardDistributions = {} local totalTickedRewardsDistributed = 0 local function tickEpoch(timestamp, blockHeight, hashchain) -- update demand factor if necessary @@ -1527,8 +1527,8 @@ addEventingHandler("distribute", utils.hasMatchingTag("Action", "Tick"), functio if #newDemandFactors > 0 then msg.ioEvent:addField("New-Demand-Factors", newDemandFactors, ";") end - if utils.lengthOfTable(tickedRewardDistributions) > 0 then - msg.ioEvent:addField("Ticked-Reward-Distributions", tickedRewardDistributions) + if totalTickedRewardsDistributed > 0 then + -- msg.ioEvent:addField("Ticked-Reward-Distributions", tickedRewardDistributions) msg.ioEvent:addField("Total-Ticked-Rewards-Distributed", totalTickedRewardsDistributed) end From af3ffe80690ef49f4a43e1d0c2198843be07da8f Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 12 Oct 2024 11:56:32 -0500 Subject: [PATCH 02/41] chore(main): add back the table, but do not popuate it with anything --- src/main.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.lua b/src/main.lua index aebbc5b..825e0e3 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1423,7 +1423,7 @@ addEventingHandler("distribute", utils.hasMatchingTag("Action", "Tick"), functio assert(msg.Timestamp, "Timestamp is required for a tick interaction") local msgTimestamp = tonumber(msg.Timestamp) -- tick and distribute rewards for every index between the last ticked epoch and the current epoch - -- local tickedRewardDistributions = {} + local tickedRewardDistributions = {} local totalTickedRewardsDistributed = 0 local function tickEpoch(timestamp, blockHeight, hashchain) -- update demand factor if necessary @@ -1528,7 +1528,7 @@ addEventingHandler("distribute", utils.hasMatchingTag("Action", "Tick"), functio msg.ioEvent:addField("New-Demand-Factors", newDemandFactors, ";") end if totalTickedRewardsDistributed > 0 then - -- msg.ioEvent:addField("Ticked-Reward-Distributions", tickedRewardDistributions) + msg.ioEvent:addField("Ticked-Reward-Distributions", tickedRewardDistributions) msg.ioEvent:addField("Total-Ticked-Rewards-Distributed", totalTickedRewardsDistributed) end From 9b5af60984c44f149937ed3b54d64c98826c14d5 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 12 Oct 2024 12:02:16 -0500 Subject: [PATCH 03/41] chore(main): add back length check --- src/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.lua b/src/main.lua index 825e0e3..09a3814 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1527,7 +1527,7 @@ addEventingHandler("distribute", utils.hasMatchingTag("Action", "Tick"), functio if #newDemandFactors > 0 then msg.ioEvent:addField("New-Demand-Factors", newDemandFactors, ";") end - if totalTickedRewardsDistributed > 0 then + if utils.lengthOfTable(tickedRewardDistributions) > 0 then msg.ioEvent:addField("Ticked-Reward-Distributions", tickedRewardDistributions) msg.ioEvent:addField("Total-Ticked-Rewards-Distributed", totalTickedRewardsDistributed) end From 6e8f825a83900634b61ff95e14c06f1036ad54ab Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 12 Oct 2024 12:36:19 -0500 Subject: [PATCH 04/41] chore(monitor): change cron to run every hour --- .github/workflows/monitor.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/monitor.yaml b/.github/workflows/monitor.yaml index 2ed0c65..7df6c69 100644 --- a/.github/workflows/monitor.yaml +++ b/.github/workflows/monitor.yaml @@ -3,7 +3,7 @@ name: IO Process Status on: workflow_dispatch: schedule: - - cron: '0 0-5,7-23 * * *' # Run every hour except 6AM UTC + - cron: '0 1-5,7-23 * * *' # Run every hour except 6AM UTC jobs: monitor: From 94cd898d4241252b74ace683750a5edf876fb436 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Mon, 14 Oct 2024 07:52:39 -0500 Subject: [PATCH 05/41] chore(monitor): do not run at 6AM UTC --- .github/workflows/monitor.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/monitor.yaml b/.github/workflows/monitor.yaml index 7df6c69..450d98f 100644 --- a/.github/workflows/monitor.yaml +++ b/.github/workflows/monitor.yaml @@ -3,7 +3,7 @@ name: IO Process Status on: workflow_dispatch: schedule: - - cron: '0 1-5,7-23 * * *' # Run every hour except 6AM UTC + - cron: '0 1-5,8-23 * * *' # Run every hour except 6AM UTC jobs: monitor: From ed0b1cd5790a566d3143ae2656ec3b83ff399c2a Mon Sep 17 00:00:00 2001 From: Philip Mataras Date: Fri, 20 Sep 2024 14:02:43 -0400 Subject: [PATCH 06/41] feat:adds new services list for each gateway. includes unit tests. --- spec/gar_spec.lua | 231 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.lua | 1 + 2 files changed, 232 insertions(+) diff --git a/spec/gar_spec.lua b/spec/gar_spec.lua index 28ca898..99ae9ed 100644 --- a/spec/gar_spec.lua +++ b/spec/gar_spec.lua @@ -33,6 +33,22 @@ local testServices = { }, }, } +local testServices = { + bundlers = { + { + fqdn = "bundler1.example.com", + port = 443, + protocol = "https", + path = "/bundler1", + }, + { + fqdn = "bundler2.example.com", + port = 443, + protocol = "https", + path = "/bundler2", + }, + }, +} local testGateway = { operatorStake = gar.getSettings().operators.minStake, @@ -97,7 +113,11 @@ describe("gar", function() gar.getSettings().operators.minStake, testSettings, nil, -- no additional services on this gateway +<<<<<<< HEAD stubGatewayAddress, +======= + "test-this-is-valid-arweave-wallet-address-1", +>>>>>>> be76626 (feat:adds new services list for each gateway. includes unit tests.) startTimestamp ) assert.is_false(status) @@ -140,7 +160,11 @@ describe("gar", function() gar.getSettings().operators.minStake, testSettings, nil, -- no additional services on this gateway +<<<<<<< HEAD stubGatewayAddress, +======= + "test-this-is-valid-arweave-wallet-address-1", +>>>>>>> be76626 (feat:adds new services list for each gateway. includes unit tests.) startTimestamp ) assert.is_true(status) @@ -355,6 +379,213 @@ describe("gar", function() assert.is_false(status) assert.match("bundler.path is required and must be a string", error) end) + it("should join the network with services and bundlers", function() + local expectation = { + operatorStake = gar.getSettings().operators.minStake, + totalDelegatedStake = 0, + vaults = {}, + delegates = {}, + startTimestamp = startTimestamp, + stats = { + prescribedEpochCount = 0, + observedEpochCount = 0, + totalEpochCount = 0, + passedEpochCount = 0, + failedEpochCount = 0, + failedConsecutiveEpochs = 0, + passedConsecutiveEpochs = 0, + }, + settings = { + allowDelegatedStaking = testSettings.allowDelegatedStaking, + delegateRewardShareRatio = testSettings.delegateRewardShareRatio, + autoStake = testSettings.autoStake, + minDelegatedStake = testSettings.minDelegatedStake, + label = testSettings.label, + fqdn = testSettings.fqdn, + protocol = testSettings.protocol, + port = testSettings.port, + properties = testSettings.properties, + }, + services = testServices, + status = "joined", + observerAddress = "test-this-is-valid-arweave-wallet-address-1", + } + + local status, result = pcall( + gar.joinNetwork, + "test-this-is-valid-arweave-wallet-address-1", + gar.getSettings().operators.minStake, + testSettings, + testServices, + "test-this-is-valid-arweave-wallet-address-1", + startTimestamp + ) + assert.is_true(status) + assert.are.equal(Balances["test-this-is-valid-arweave-wallet-address-1"], 0) + assert.are.same(expectation, result) + assert.are.same(expectation, gar.getGateway("test-this-is-valid-arweave-wallet-address-1")) + end) + it("should fail to join the network with invalid services key", function() + local invalidServices = { + invalidKey = {}, -- Invalid key not allowed + } + local status, error = pcall( + gar.joinNetwork, + "test-this-is-valid-arweave-wallet-address-1", + gar.getSettings().operators.minStake, + testSettings, + invalidServices, + "test-this-is-valid-arweave-wallet-address-1", + startTimestamp + ) + assert.is_false(status) + assert.match("services contains an invalid key", error) + end) + it("should fail to join the network with invalid bundler keys", function() + local servicesWithInvalidBundler = { + bundlers = { + { + fqdn = "bundler1.example.com", + port = 443, + protocol = "https", + path = "/bundler1", + invalidKey = "invalid", -- Invalid key in bundler + }, + }, + } + local status, error = pcall( + gar.joinNetwork, + "test-this-is-valid-arweave-wallet-address-1", + gar.getSettings().operators.minStake, + testSettings, + servicesWithInvalidBundler, + "test-this-is-valid-arweave-wallet-address-1", + startTimestamp + ) + assert.is_false(status) + assert.match("bundler contains an invalid key", error) + end) + it("should fail to join the network with too many bundlers", function() + local servicesWithTooManyBundlers = { + bundlers = {}, + } + for i = 1, 21 do -- Exceeding the maximum of 20 bundlers + table.insert(servicesWithTooManyBundlers.bundlers, { + fqdn = "bundler" .. i .. ".example.com", + port = 443, + protocol = "https", + path = "/bundler" .. i, + }) + end + + local status, error = pcall( + gar.joinNetwork, + "test-this-is-valid-arweave-wallet-address-1", + gar.getSettings().operators.minStake, + testSettings, + servicesWithTooManyBundlers, + "test-this-is-valid-arweave-wallet-address-1", + startTimestamp + ) + assert.is_false(status) + assert.match("No more than 20 bundlers allowed", error) + end) + it("should fail to join the network with invalid bundler fqdn", function() + local servicesWithInvalidFqdn = { + bundlers = { + { + fqdn = 20, -- Invalid fqdn (a number) + port = 443, + protocol = "https", + path = "/bundler", + }, + }, + } + + local status, error = pcall( + gar.joinNetwork, + "test-this-is-valid-arweave-wallet-address-1", + gar.getSettings().operators.minStake, + testSettings, + servicesWithInvalidFqdn, + "test-this-is-valid-arweave-wallet-address-1", + startTimestamp + ) + assert.is_false(status) + assert.match("bundler.fqdn is required and must be a string", error) + end) + it("should fail to join the network with invalid bundler port", function() + local servicesWithInvalidPort = { + bundlers = { + { + fqdn = "bundler.example.com", + port = -1, -- Invalid port (negative number) + protocol = "https", + path = "/bundler", + }, + }, + } + + local status, error = pcall( + gar.joinNetwork, + "test-this-is-valid-arweave-wallet-address-1", + gar.getSettings().operators.minStake, + testSettings, + servicesWithInvalidPort, + "test-this-is-valid-arweave-wallet-address-1", + startTimestamp + ) + assert.is_false(status) + assert.match("bundler.port must be an integer between 0 and 65535", error) + end) + it("should fail to join the network with invalid bundler protocol", function() + local servicesWithInvalidProtocol = { + bundlers = { + { + fqdn = "bundler.example.com", + port = 443, + protocol = "ftp", -- Invalid protocol (should be 'https') + path = "/bundler", + }, + }, + } + + local status, error = pcall( + gar.joinNetwork, + "test-this-is-valid-arweave-wallet-address-1", + gar.getSettings().operators.minStake, + testSettings, + servicesWithInvalidProtocol, + "test-this-is-valid-arweave-wallet-address-1", + startTimestamp + ) + assert.is_false(status) + assert.match("bundler.protocol is required and must be 'https'", error) + end) + it("should fail to join the network with invalid bundler path", function() + local servicesWithInvalidPath = { + bundlers = { + { + fqdn = "bundler.example.com", + port = 443, + protocol = "https", + path = nil, -- Invalid path (nil value) + }, + }, + } + + local status, error = pcall( + gar.joinNetwork, + "test-this-is-valid-arweave-wallet-address-1", + gar.getSettings().operators.minStake, + testSettings, + servicesWithInvalidPath, + "test-this-is-valid-arweave-wallet-address-1", + startTimestamp + ) + assert.is_false(status) + assert.match("bundler.path is required and must be a string", error) + end) end) describe("leaveNetwork", function() diff --git a/src/main.lua b/src/main.lua index 09a3814..dea70ef 100644 --- a/src/main.lua +++ b/src/main.lua @@ -788,6 +788,7 @@ addEventingHandler(ActionMap.JoinNetwork, utils.hasMatchingTag("Action", ActionM delegateRewardShareRatio = tonumber(msg.Tags["Delegate-Reward-Share-Ratio"]) or 0, properties = msg.Tags.Properties or "FH1aVetOoulPGqgYukj0VE0wIhDy90WiQoV3U2PeY44", autoStake = msg.Tags["Auto-Stake"] == "true", + services = json.decode(msg.Tags.Services) or {}, } local updatedServices = utils.safeDecodeJson(msg.Tags.Services) From 8b80669904c0a0e7280ab8a64ea48dfbc927cd90 Mon Sep 17 00:00:00 2001 From: Philip Mataras Date: Tue, 24 Sep 2024 13:02:28 -0400 Subject: [PATCH 07/41] fix:used stylua to cleanup formatting --- docs/auctions.md | 33 +++++++++++++++++++++++++++++++++ spec/gar_spec.lua | 46 +++++++++++++++++++--------------------------- 2 files changed, 52 insertions(+), 27 deletions(-) create mode 100644 docs/auctions.md diff --git a/docs/auctions.md b/docs/auctions.md new file mode 100644 index 0000000..a4c32f7 --- /dev/null +++ b/docs/auctions.md @@ -0,0 +1,33 @@ +```mermaid +sequenceDiagram +participant Owner/Initiator +participant Bidder +participant ANTProcess +participant ProtocolBalance +participant ARNSRegistry + + Owner/Initiator ->> ANTProcess: Send "Release-Name" message with tags + alt Owner/Initiator is not owner of ANT process + ANTProcess ->> Owner/Initiator: Invalid-Return-Name-Notice + else Owner/Initiator is owner of ANT process + ANTProcess ->> ARNSRegistry: Notify registry with `Release-Name` with [Name, Recipient] + alt Name does not exist or does not map to process id + ARNSRegistry ->> Owner/Initiator: Invalid-Return-Name-Notice (could be to ANT) + else Name exists and maps to name + ARNSRegistry ->> ARNSRegistry: Create auction for name + ARNSRegistry ->> ARNSRegistry: Accept bids for auction + alt Valid bid received before expiration + Bidder ->> ARNSRegistry: Send bid message with process-id + ARNSRegistry ->> ARNSRegistry: Validate bid and calculate payouts + ARNSRegistry ->> ARNSRegistry: Name added to registry + ARNSRegistry ->> ProtocolBalance: Transfer 50% of proceeds + ARNSRegistry ->> Owner/Initiator: Transfer 50% of proceeds + ARNSRegistry ->> Bidder: Send Auction-Bid-Success-Notice + else Invalid bid received before expiration (insufficient balance, too low, etc.) + ARNSRegistry ->> Bidder: Auction-Bid-Failure-Notice + else No bid received, auction expires + ARNSRegistry ->> ARNSRegistry: Release name, no payouts + end + end + end +``` diff --git a/spec/gar_spec.lua b/spec/gar_spec.lua index 99ae9ed..d230ead 100644 --- a/spec/gar_spec.lua +++ b/spec/gar_spec.lua @@ -113,11 +113,7 @@ describe("gar", function() gar.getSettings().operators.minStake, testSettings, nil, -- no additional services on this gateway -<<<<<<< HEAD stubGatewayAddress, -======= - "test-this-is-valid-arweave-wallet-address-1", ->>>>>>> be76626 (feat:adds new services list for each gateway. includes unit tests.) startTimestamp ) assert.is_false(status) @@ -160,11 +156,7 @@ describe("gar", function() gar.getSettings().operators.minStake, testSettings, nil, -- no additional services on this gateway -<<<<<<< HEAD stubGatewayAddress, -======= - "test-this-is-valid-arweave-wallet-address-1", ->>>>>>> be76626 (feat:adds new services list for each gateway. includes unit tests.) startTimestamp ) assert.is_true(status) @@ -408,22 +400,22 @@ describe("gar", function() }, services = testServices, status = "joined", - observerAddress = "test-this-is-valid-arweave-wallet-address-1", + observerAddress = stubGatewayAddress, } local status, result = pcall( gar.joinNetwork, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, gar.getSettings().operators.minStake, testSettings, testServices, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, startTimestamp ) assert.is_true(status) - assert.are.equal(Balances["test-this-is-valid-arweave-wallet-address-1"], 0) + assert.are.equal(Balances[stubGatewayAddress], 0) assert.are.same(expectation, result) - assert.are.same(expectation, gar.getGateway("test-this-is-valid-arweave-wallet-address-1")) + assert.are.same(expectation, gar.getGateway(stubGatewayAddress)) end) it("should fail to join the network with invalid services key", function() local invalidServices = { @@ -431,11 +423,11 @@ describe("gar", function() } local status, error = pcall( gar.joinNetwork, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, gar.getSettings().operators.minStake, testSettings, invalidServices, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, startTimestamp ) assert.is_false(status) @@ -455,11 +447,11 @@ describe("gar", function() } local status, error = pcall( gar.joinNetwork, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, gar.getSettings().operators.minStake, testSettings, servicesWithInvalidBundler, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, startTimestamp ) assert.is_false(status) @@ -480,11 +472,11 @@ describe("gar", function() local status, error = pcall( gar.joinNetwork, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, gar.getSettings().operators.minStake, testSettings, servicesWithTooManyBundlers, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, startTimestamp ) assert.is_false(status) @@ -504,11 +496,11 @@ describe("gar", function() local status, error = pcall( gar.joinNetwork, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, gar.getSettings().operators.minStake, testSettings, servicesWithInvalidFqdn, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, startTimestamp ) assert.is_false(status) @@ -528,11 +520,11 @@ describe("gar", function() local status, error = pcall( gar.joinNetwork, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, gar.getSettings().operators.minStake, testSettings, servicesWithInvalidPort, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, startTimestamp ) assert.is_false(status) @@ -552,11 +544,11 @@ describe("gar", function() local status, error = pcall( gar.joinNetwork, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, gar.getSettings().operators.minStake, testSettings, servicesWithInvalidProtocol, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, startTimestamp ) assert.is_false(status) @@ -576,11 +568,11 @@ describe("gar", function() local status, error = pcall( gar.joinNetwork, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, gar.getSettings().operators.minStake, testSettings, servicesWithInvalidPath, - "test-this-is-valid-arweave-wallet-address-1", + stubGatewayAddress, startTimestamp ) assert.is_false(status) From a6e65da9f5a4e67ca59026ad4a88cd116661df87 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 8 Oct 2024 17:40:28 -0500 Subject: [PATCH 08/41] chore(test): fix tests --- docs/auctions.md | 33 --------------------------------- src/main.lua | 1 - 2 files changed, 34 deletions(-) delete mode 100644 docs/auctions.md diff --git a/docs/auctions.md b/docs/auctions.md deleted file mode 100644 index a4c32f7..0000000 --- a/docs/auctions.md +++ /dev/null @@ -1,33 +0,0 @@ -```mermaid -sequenceDiagram -participant Owner/Initiator -participant Bidder -participant ANTProcess -participant ProtocolBalance -participant ARNSRegistry - - Owner/Initiator ->> ANTProcess: Send "Release-Name" message with tags - alt Owner/Initiator is not owner of ANT process - ANTProcess ->> Owner/Initiator: Invalid-Return-Name-Notice - else Owner/Initiator is owner of ANT process - ANTProcess ->> ARNSRegistry: Notify registry with `Release-Name` with [Name, Recipient] - alt Name does not exist or does not map to process id - ARNSRegistry ->> Owner/Initiator: Invalid-Return-Name-Notice (could be to ANT) - else Name exists and maps to name - ARNSRegistry ->> ARNSRegistry: Create auction for name - ARNSRegistry ->> ARNSRegistry: Accept bids for auction - alt Valid bid received before expiration - Bidder ->> ARNSRegistry: Send bid message with process-id - ARNSRegistry ->> ARNSRegistry: Validate bid and calculate payouts - ARNSRegistry ->> ARNSRegistry: Name added to registry - ARNSRegistry ->> ProtocolBalance: Transfer 50% of proceeds - ARNSRegistry ->> Owner/Initiator: Transfer 50% of proceeds - ARNSRegistry ->> Bidder: Send Auction-Bid-Success-Notice - else Invalid bid received before expiration (insufficient balance, too low, etc.) - ARNSRegistry ->> Bidder: Auction-Bid-Failure-Notice - else No bid received, auction expires - ARNSRegistry ->> ARNSRegistry: Release name, no payouts - end - end - end -``` diff --git a/src/main.lua b/src/main.lua index dea70ef..09a3814 100644 --- a/src/main.lua +++ b/src/main.lua @@ -788,7 +788,6 @@ addEventingHandler(ActionMap.JoinNetwork, utils.hasMatchingTag("Action", ActionM delegateRewardShareRatio = tonumber(msg.Tags["Delegate-Reward-Share-Ratio"]) or 0, properties = msg.Tags.Properties or "FH1aVetOoulPGqgYukj0VE0wIhDy90WiQoV3U2PeY44", autoStake = msg.Tags["Auto-Stake"] == "true", - services = json.decode(msg.Tags.Services) or {}, } local updatedServices = utils.safeDecodeJson(msg.Tags.Services) From 25afa9ff56080feca1ad3736f5814484a98e4c43 Mon Sep 17 00:00:00 2001 From: Philip Mataras Date: Tue, 15 Oct 2024 13:41:08 -0400 Subject: [PATCH 09/41] feat: adds instant withdraw --- spec/gar_spec.lua | 247 ++++++++++++++++++++++++++++++++++++++++ src/constants.lua | 2 + src/gar.lua | 101 ++++++++++++++-- src/main.lua | 73 +++++++++++- tests/gar.test.mjs | 122 ++++++++++++++++++-- tests/handlers.test.mjs | 2 +- 6 files changed, 526 insertions(+), 21 deletions(-) diff --git a/spec/gar_spec.lua b/spec/gar_spec.lua index d230ead..5c122cf 100644 --- a/spec/gar_spec.lua +++ b/spec/gar_spec.lua @@ -1056,6 +1056,253 @@ describe("gar", function() assert.are.same(expectation, result) assert.are.same(expectation, gar.getGateway(stubGatewayAddress)) end) + + it("should decrease delegated stake with instant withdrawal and apply penalty and remove delegate", function() + Balances[ao.id] = 0 + local penaltyAmount = 1000 * 0.80 + local withdrawalAmount = 1000 - penaltyAmount + GatewayRegistry[stubGatewayAddress] = { + operatorStake = gar.getSettings().operators.minStake, + totalDelegatedStake = gar.getSettings().delegates.minStake + 1000, + vaults = {}, + startTimestamp = startTimestamp, + stats = { + prescribedEpochCount = 0, + observedEpochCount = 0, + totalEpochCount = 0, + passedEpochCount = 0, + failedEpochCount = 0, + failedConsecutiveEpochs = 0, + passedConsecutiveEpochs = 0, + }, + settings = testSettings, + status = "joined", + observerAddress = stubObserverAddress, + delegates = { + [stubRandomAddress] = { + delegatedStake = gar.getSettings().delegates.minStake + 1000, + startTimestamp = 0, + vaults = {}, + }, + }, + } + + local status, result = pcall( + gar.decreaseDelegateStake, + stubGatewayAddress, + stubRandomAddress, + 1000, + startTimestamp, + stubMessageId, + true -- instant withdrawal + ) + + assert.is_true(status) + assert.are.same(result.delegates[stubRandomAddress].delegatedStake, gar.getSettings().delegates.minStake) + assert.are.equal(result.totalDelegatedStake, gar.getSettings().delegates.minStake) + assert.are.equal(withdrawalAmount, Balances[stubRandomAddress]) + assert.are.equal(penaltyAmount, Balances[ao.id]) + assert.are.equal( + gar.getSettings().delegates.minStake, + _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake + ) + end) + + it( + "should successfully convert a standard delegate withdraw to instant with maximum penalty and remove delegate", + function() + -- Setup a valid gateway with a delegate vault + local vaultId = "vault_id_1" + local currentTimestamp = 1000000 + local startTimestamp = 1000000 + local vaultBalance = 1000 + local expectedPenaltyRate = 0.80 + local expectedPenaltyAmount = vaultBalance * expectedPenaltyRate + local expectedWithdrawalAmount = vaultBalance - expectedPenaltyAmount + + Balances[ao.id] = 0 + + _G.GatewayRegistry[stubGatewayAddress] = { + operatorStake = gar.getSettings().operators.minStake + vaultBalance, + totalDelegatedStake = 0, + vaults = {}, + delegates = { + [stubRandomAddress] = { + delegatedStake = 0, + startTimestamp = startTimestamp, + vaults = { + [vaultId] = { + balance = vaultBalance, + startTimestamp = startTimestamp, + }, + }, + }, + }, + startTimestamp = startTimestamp, + stats = { + prescribedEpochCount = 0, + observedEpochCount = 0, + totalEpochCount = 0, + passedEpochCount = 0, + failedEpochCount = 0, + failedConsecutiveEpochs = 0, + passedConsecutiveEpochs = 0, + }, + settings = testSettings, + status = "joined", + observerAddress = stubObserverAddress, + } + + local status, result = pcall( + gar.instantDelegateWithdrawal, + stubRandomAddress, + stubGatewayAddress, + vaultId, + currentTimestamp + ) + + assert.is_true(status) + assert.are.equal(nil, result.delegate) -- Delegate should be removed after full withdrawal + assert.are.equal(0, result.totalDelegatedStake) + assert.are.equal(expectedWithdrawalAmount, Balances[stubRandomAddress]) + assert.are.equal(expectedPenaltyAmount, Balances[ao.id]) + assert.are.equal(0, _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake) + end + ) + + it( + "should withdraw delegate stake and apply reduced penalty based on elapsed time with remaining vault", + function() + -- Setup a valid gateway with a delegate vault + local vaultId = "vault_id_1" + local remainingDelegateStakeBalance = 1000 + local startTimestamp = 500000 + local elapsedTime = 15 * 24 * 60 * 60 * 1000 -- Half of 30 days in milliseconds + local currentTimestamp = startTimestamp + elapsedTime + local vaultBalance = 1000 + local maxPenalty = 0.80 + local minPenalty = 0.05 + local penaltyRate = maxPenalty + - ((maxPenalty - minPenalty) * (elapsedTime / gar.getSettings().delegates.withdrawLengthMs)) + local expectedPenaltyAmount = math.floor(vaultBalance * penaltyRate) + local expectedWithdrawalAmount = vaultBalance - expectedPenaltyAmount + Balances[ao.id] = 0 + + _G.GatewayRegistry[stubGatewayAddress] = { + operatorStake = gar.getSettings().operators.minStake, + totalDelegatedStake = remainingDelegateStakeBalance, + vaults = {}, + delegates = { + [stubRandomAddress] = { + delegatedStake = remainingDelegateStakeBalance, + startTimestamp = startTimestamp, + vaults = { + [vaultId] = { + balance = vaultBalance, + startTimestamp = startTimestamp, + }, + }, + }, + }, + startTimestamp = startTimestamp, + stats = { + prescribedEpochCount = 0, + observedEpochCount = 0, + totalEpochCount = 0, + passedEpochCount = 0, + failedEpochCount = 0, + failedConsecutiveEpochs = 0, + passedConsecutiveEpochs = 0, + }, + settings = testSettings, + status = "joined", + observerAddress = stubObserverAddress, + } + + local status, result = pcall( + gar.instantDelegateWithdrawal, + stubRandomAddress, + stubGatewayAddress, + vaultId, + currentTimestamp + ) + + assert.is_true(status) + assert.are.equal(nil, next(result.delegate.vaults)) -- Delegate should have no vaults remaining + assert.are.equal(remainingDelegateStakeBalance, result.totalDelegatedStake) + assert.are.equal(expectedWithdrawalAmount, Balances[stubRandomAddress]) + assert.are.equal(expectedPenaltyAmount, Balances[ao.id]) + assert.are.equal( + remainingDelegateStakeBalance, + _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake + ) + end + ) + + it( + "should withdraw delegate stake and apply reduced penalty based on more elapsed time and remove delegate", + function() + -- Setup a valid gateway with a delegate vault + local vaultId = "vault_id_1" + local vaultBalance = 1000 + local startTimestamp = 500000 + local elapsedTime = 29 * 24 * 60 * 60 * 1000 -- Half of 30 days in milliseconds + local currentTimestamp = startTimestamp + elapsedTime + local maxPenalty = 0.80 + local minPenalty = 0.05 + local penaltyRate = maxPenalty + - ((maxPenalty - minPenalty) * (elapsedTime / gar.getSettings().delegates.withdrawLengthMs)) + local expectedPenaltyAmount = math.floor(vaultBalance * penaltyRate) + local expectedWithdrawalAmount = vaultBalance - expectedPenaltyAmount + Balances[ao.id] = 0 + + _G.GatewayRegistry[stubGatewayAddress] = { + operatorStake = gar.getSettings().operators.minStake, + totalDelegatedStake = 0, + vaults = {}, + delegates = { + [stubRandomAddress] = { + delegatedStake = 0, + startTimestamp = startTimestamp, + vaults = { + [vaultId] = { + balance = vaultBalance, + startTimestamp = startTimestamp, + }, + }, + }, + }, + startTimestamp = startTimestamp, + stats = { + prescribedEpochCount = 0, + observedEpochCount = 0, + totalEpochCount = 0, + passedEpochCount = 0, + failedEpochCount = 0, + failedConsecutiveEpochs = 0, + passedConsecutiveEpochs = 0, + }, + settings = testSettings, + status = "joined", + observerAddress = stubObserverAddress, + } + + local status, result = pcall( + gar.instantDelegateWithdrawal, + stubRandomAddress, + stubGatewayAddress, + vaultId, + currentTimestamp + ) + + assert.is_true(status) + assert.are.equal(nil, result.delegate) -- Delegate should be removed after full withdrawal + assert.are.equal(0, result.totalDelegatedStake) + assert.are.equal(expectedWithdrawalAmount, Balances[stubRandomAddress]) + assert.are.equal(expectedPenaltyAmount, Balances[ao.id]) + assert.are.equal(0, _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake) + end + ) end) describe("slashOperatorStake", function() diff --git a/src/constants.lua b/src/constants.lua index 916658e..474858b 100644 --- a/src/constants.lua +++ b/src/constants.lua @@ -7,6 +7,8 @@ constants.oneYearSeconds = 60 * 60 * 24 * 365 constants.thirtyDaysSeconds = 60 * 60 * 24 * 30 constants.defaultundernameLimit = 10 constants.totalTokenSupply = 1000000000 * 1000000 -- 1 billion tokens +constants.MIN_WITHDRAW_PENALTY = 0.20 +constants.MAX_WITHDRAW_PENALTY = 0.80 -- ARNS constants.DEFAULT_UNDERNAME_COUNT = 10 diff --git a/src/gar.lua b/src/gar.lua index cd0e51f..220507d 100644 --- a/src/gar.lua +++ b/src/gar.lua @@ -345,7 +345,7 @@ function gar.getSettings() return utils.deepCopy(GatewayRegistrySettings) end -function gar.decreaseDelegateStake(gatewayAddress, delegator, qty, currentTimestamp, messageId) +function gar.decreaseDelegateStake(gatewayAddress, delegator, qty, currentTimestamp, messageId, instantWithdraw) assert(type(qty) == "number", "Quantity is required and must be a number") assert(qty > 0, "Quantity must be greater than 0") @@ -369,18 +369,40 @@ function gar.decreaseDelegateStake(gatewayAddress, delegator, qty, currentTimest error("Remaining delegated stake must be greater than the minimum delegated stake amount.") end - -- Withdraw the delegate's stake + -- Instant withdrawal logic with penalty + if instantWithdraw then + -- Unlock the tokens from the gateway and delegate + gateway.delegates[delegator].delegatedStake = gateway.delegates[delegator].delegatedStake - qty + gateway.totalDelegatedStake = gateway.totalDelegatedStake - qty - local newDelegateVault = { - balance = qty, - startTimestamp = currentTimestamp, - endTimestamp = currentTimestamp + gar.getSettings().delegates.withdrawLengthMs, - } + -- Calculate the penalty amount + local maxPenalty = 0.80 + local penaltyAmount = qty * maxPenalty + local amountToWithdraw = qty - penaltyAmount + + -- Add penalty to AR.IO protocol balance + balances.increaseBalance(ao.id, penaltyAmount) + + -- Withdraw the remaining tokens to the delegate + balances.increaseBalance(delegator, amountToWithdraw) + + -- Remove the delegate if no stake is left + if gateway.delegates[delegator].delegatedStake == 0 and next(gateway.delegates[delegator].vaults) == nil then + gateway.delegates[delegator] = nil + end + else + -- Withdraw the delegate's stake + local newDelegateVault = { + balance = qty, + startTimestamp = currentTimestamp, + endTimestamp = currentTimestamp + gar.getSettings().delegates.withdrawLengthMs, + } - -- Lock the qty in a vault to be unlocked after withdrawal period and decrease the gateway's total delegated stake - gateway.delegates[delegator].vaults[messageId] = newDelegateVault - gateway.delegates[delegator].delegatedStake = gateway.delegates[delegator].delegatedStake - qty - gateway.totalDelegatedStake = gateway.totalDelegatedStake - qty + -- Lock the qty in a vault to be unlocked after withdrawal period and decrease the gateway's total delegated stake + gateway.delegates[delegator].vaults[messageId] = newDelegateVault + gateway.delegates[delegator].delegatedStake = gateway.delegates[delegator].delegatedStake - qty + gateway.totalDelegatedStake = gateway.totalDelegatedStake - qty + end -- update the gateway GatewayRegistry[gatewayAddress] = gateway return gar.getGateway(gatewayAddress) @@ -756,4 +778,61 @@ function gar.cancelDelegateWithdrawal(from, gatewayAddress, vaultId) } end +function gar.instantDelegateWithdrawal(from, gatewayAddress, vaultId, currentTimestamp) + local gateway = gar.getGateway(gatewayAddress) + if gateway == nil then + error("Gateway does not exist") + end + + local delegate = gateway.delegates[from] + if delegate == nil then + error("Delegate does not exist") + end + + local vault = delegate.vaults[vaultId] + if vault == nil then + error("Vault does not exist") + end + + -- Calculate elapsed time since the withdrawal started + local elapsedTime = currentTimestamp - vault.startTimestamp + local totalWithdrawalTime = gar.getSettings().delegates.withdrawLengthMs + + -- Ensure the elapsed time is not negative + if elapsedTime < 0 then + error("Invalid elapsed time") + end + + -- Calculate the penalty rate based on elapsed time + local maxPenalty = 0.80 + local minPenalty = 0.05 + local penaltyRate = maxPenalty - ((maxPenalty - minPenalty) * (elapsedTime / totalWithdrawalTime)) + penaltyRate = math.max(minPenalty, math.min(maxPenalty, penaltyRate)) -- Ensure penalty is within bounds + + -- Calculate the penalty amount and the amount to withdraw + local vaultBalance = vault.balance + + local penaltyAmount = math.floor(vaultBalance * penaltyRate) + local amountToWithdraw = vaultBalance - penaltyAmount + + -- Add penalty to AR.IO protocol balance + balances.increaseBalance(ao.id, penaltyAmount) + balances.increaseBalance(from, amountToWithdraw) + + -- Remove the vault after withdrawal + delegate.vaults[vaultId] = nil + + -- Remove the delegate if no stake is left + if delegate.delegatedStake == 0 and next(delegate.vaults) == nil then + gateway.delegates[from] = nil + end + + -- Update the gateway + GatewayRegistry[gatewayAddress] = gateway + return { + delegate = gar.getGateway(gatewayAddress).delegates[from], + totalDelegatedStake = gateway.totalDelegatedStake, + } +end + return gar diff --git a/src/main.lua b/src/main.lua index 09a3814..f038149 100644 --- a/src/main.lua +++ b/src/main.lua @@ -82,6 +82,7 @@ local ActionMap = { DelegateStake = "Delegate-Stake", DecreaseDelegateStake = "Decrease-Delegate-Stake", CancelDelegateWithdrawal = "Cancel-Delegate-Withdrawal", + InstantDelegateWithdrawal = "Instant-Delegate-Withdrawal", } -- Low fidelity trackers @@ -1113,6 +1114,68 @@ addEventingHandler( end ) +addEventingHandler( + ActionMap.InstantDelegateWithdrawal, + utils.hasMatchingTag("Action", ActionMap.InstantDelegateWithdrawal), + function(msg) + local checkAssertions = function() + assert(utils.isValidAOAddress(msg.Tags.Target or msg.Tags.Address), "Invalid gateway address") + assert(utils.isValidAOAddress(msg.Tags["Vault-Id"]), "Invalid vault id") + end + + local shouldContinue = eventingPcall(msg.ioEvent, function(error) + ao.send({ + Target = msg.From, + Tags = { Action = "Invalid-Instant-Delegate-Withdrawal-Notice", Error = "Bad-Input" }, + Data = tostring(error), + }) + end, checkAssertions) + if not shouldContinue then + return + end + + local gatewayAddress = utils.formatAddress(msg.Tags.Target or msg.Tags.Address) + local fromAddress = utils.formatAddress(msg.From) + local vaultId = msg.Tags["Vault-Id"] + msg.ioEvent:addField("TargetFormatted", gatewayAddress) + + local shouldContinue2, result = eventingPcall(msg.ioEvent, function(error) + ao.send({ + Target = msg.From, + Tags = { + Action = "Invalid-Instant-Delegate-Withdrawal-Notice", + Error = "Invalid-Instant-Delegate-Withdrawal", + }, + Data = tostring(error), + }) + end, gar.instantDelegateWithdrawal, fromAddress, gatewayAddress, vaultId, msg.Timestamp) + if not shouldContinue2 then + return + end + + local delegateResult = {} + if result ~= nil then + if result.delegate ~= nil then + delegateResult = result.delegate + local newStake = delegateResult.delegatedStake + msg.ioEvent:addField("PreviousStake", newStake - delegateResult.vaults[vaultId].balance) + msg.ioEvent:addField("NewStake", newStake) + msg.ioEvent:addField("GatewayTotalDelegatedStake", result.totalDelegatedStake) + end + end + + ao.send({ + Target = msg.From, + Tags = { + Action = "Instant-Delegate-Withdrawal-Notice", + Address = gatewayAddress, + ["Vault-Id"] = msg.Tags["Vault-Id"], + }, + Data = json.encode(delegateResult), + }) + end +) + addEventingHandler( ActionMap.DecreaseDelegateStake, utils.hasMatchingTag("Action", ActionMap.DecreaseDelegateStake), @@ -1123,6 +1186,9 @@ addEventingHandler( tonumber(msg.Tags.Quantity) > 0 and utils.isInteger(tonumber(msg.Tags.Quantity)), "Invalid quantity. Must be integer greater than 0" ) + if msg.Tags.Instant ~= nil then + assert(type(msg.Tags.Instant) == "boolean", "Instant must be a boolean value") + end end local shouldContinue = eventingPcall(msg.ioEvent, function(error) @@ -1139,6 +1205,7 @@ addEventingHandler( local from = utils.formatAddress(msg.From) local target = utils.formatAddress(msg.Tags.Target or msg.Tags.Address) local quantity = tonumber(msg.Tags.Quantity) + local instantWithdraw = msg.Tags.Instant == true msg.ioEvent:addField("TargetFormatted", target) local shouldContinue2, gateway = eventingPcall(msg.ioEvent, function(error) @@ -1147,7 +1214,7 @@ addEventingHandler( Tags = { Action = "Invalid-Decrease-Delegate-Stake-Notice", Error = "Invalid-Decrease-Delegate-Stake" }, Data = tostring(error), }) - end, gar.decreaseDelegateStake, target, from, quantity, msg.Timestamp, msg.Id) + end, gar.decreaseDelegateStake, target, from, quantity, msg.Timestamp, msg.Id, instantWithdraw) if not shouldContinue2 then return end @@ -1158,7 +1225,9 @@ addEventingHandler( msg.ioEvent:addField("PreviousStake", newStake + quantity) msg.ioEvent:addField("NewStake", newStake) msg.ioEvent:addField("GatewayTotalDelegatedStake", gateway.totalDelegatedStake) - + if msg.Tags.Instant == true then + msg.ioEvent:addField("InstantWithdrawal", true) + end delegateResult = gateway.delegates[from] local newDelegateVaults = delegateResult.vaults if newDelegateVaults ~= nil then diff --git a/tests/gar.test.mjs b/tests/gar.test.mjs index 9914cc7..705534b 100644 --- a/tests/gar.test.mjs +++ b/tests/gar.test.mjs @@ -708,7 +708,7 @@ describe('GatewayRegistry', async () => { Tags: [ { name: 'Action', value: 'Transfer' }, { name: 'Recipient', value: newStubAddress }, - { name: 'Quantity', value: '1000000000' }, // 1K IO + { name: 'Quantity', value: '2000000000' }, // 2K IO ], }, joinNetworkResult.Memory, @@ -724,7 +724,7 @@ describe('GatewayRegistry', async () => { Timestamp: stubbedTimestamp, Tags: [ { name: 'Action', value: 'Delegate-Stake' }, - { name: 'Quantity', value: '1000000000' }, // 1K IO + { name: 'Quantity', value: '2000000000' }, // 2K IO { name: 'Address', value: STUB_ADDRESS }, ], }, @@ -745,14 +745,14 @@ describe('GatewayRegistry', async () => { assert.deepEqual( { [newStubAddress]: { - delegatedStake: 1_000_000_000, + delegatedStake: 2_000_000_000, startTimestamp: stubbedTimestamp, vaults: [], }, }, gatewayData.delegates, ); - assert.deepEqual(gatewayData.totalDelegatedStake, 1_000_000_000); + assert.deepEqual(gatewayData.totalDelegatedStake, 2_000_000_000); sharedMemory = delegateStakeResult.Memory; }); @@ -786,7 +786,7 @@ describe('GatewayRegistry', async () => { const gatewayData = JSON.parse(gateway.Messages[0].Data); assert.deepEqual(gatewayData.delegates, { [newStubAddress]: { - delegatedStake: 0, + delegatedStake: 1_000_000_000, startTimestamp: stubbedTimestamp, vaults: { [''.padEnd(43, 'x')]: { @@ -797,7 +797,7 @@ describe('GatewayRegistry', async () => { }, }, }); - assert.deepEqual(gatewayData.totalDelegatedStake, 0); + assert.deepEqual(gatewayData.totalDelegatedStake, 1_000_000_000); sharedMemory = decreaseStakeResult.Memory; }); @@ -830,6 +830,49 @@ describe('GatewayRegistry', async () => { ); const gatewayData = JSON.parse(gateway.Messages[0].Data); + + assert.deepEqual(gatewayData.delegates, { + [newStubAddress]: { + delegatedStake: 2_000_000_000, + startTimestamp: stubbedTimestamp, + vaults: [], + }, + }); + assert.deepEqual(gatewayData.totalDelegatedStake, 2_000_000_000); + sharedMemory = cancelWithdrawalResult.Memory; + }); + + it('should decrease delegate stake with instant withdrawal', async () => { + const instantWithdrawalTimestamp = stubbedTimestamp + 1000 * 60 * 15; // 15 minutes after stubbedTimestamp + const decreaseStakeResult = await handle( + { + From: newStubAddress, + Owner: newStubAddress, + Timestamp: instantWithdrawalTimestamp, + Id: ''.padEnd(43, 'x'), + Tags: [ + { name: 'Action', value: 'Decrease-Delegate-Stake' }, + { name: 'Address', value: STUB_ADDRESS }, + { name: 'Quantity', value: '1000000000' }, // 1K IO + { name: 'Instant', value: true }, + ], + }, + sharedMemory, + ); + + // get the updated gateway record + const gateway = await handle( + { + Tags: [ + { name: 'Action', value: 'Gateway' }, + { name: 'Address', value: STUB_ADDRESS }, + ], + Timestamp: instantWithdrawalTimestamp + 1, + }, + decreaseStakeResult.Memory, + ); + const gatewayData = JSON.parse(gateway.Messages[0].Data); + // Assertions assert.deepEqual(gatewayData.delegates, { [newStubAddress]: { delegatedStake: 1_000_000_000, @@ -838,7 +881,72 @@ describe('GatewayRegistry', async () => { }, }); assert.deepEqual(gatewayData.totalDelegatedStake, 1_000_000_000); - sharedMemory = cancelWithdrawalResult.Memory; + sharedMemory = decreaseStakeResult.Memory; + + }); + + it('should allow decrease delegate stake from a gateway followed up with instant withdrawal', async () => { + const decreaseStakeTimestamp = stubbedTimestamp + 1000 * 60 * 15; // 15 minutes after stubbedTimestamp + const decreaseStakeResult = await handle( + { + From: newStubAddress, + Owner: newStubAddress, + Timestamp: decreaseStakeTimestamp, + Id: ''.padEnd(43, 'x'), + Tags: [ + { name: 'Action', value: 'Decrease-Delegate-Stake' }, + { name: 'Address', value: STUB_ADDRESS }, + { name: 'Quantity', value: '1000000000' }, // 1K IO + ], + }, + sharedMemory, + ); + + // get the updated gateway record + let gateway = await handle( + { + Tags: [ + { name: 'Action', value: 'Gateway' }, + { name: 'Address', value: STUB_ADDRESS }, + ], + Timestamp: decreaseStakeTimestamp + 1, + }, + decreaseStakeResult.Memory, + ); + let gatewayData = JSON.parse(gateway.Messages[0].Data); + + const instantDecreaseStakeTimestamp = decreaseStakeTimestamp + 1000 * 60 * 60; // 60 minutes after stubbedTimestamp + const instantDecreaseStakeResult = await handle( + { + From: newStubAddress, + Owner: newStubAddress, + Timestamp: instantDecreaseStakeTimestamp, + Id: ''.padEnd(43, 'x'), + Tags: [ + { name: 'Action', value: 'Instant-Delegate-Withdrawal' }, // TO DO - MAKE THIS HANDLER! + { name: 'Address', value: STUB_ADDRESS }, + { name: 'Vault-Id', value: ''.padEnd(43, 'x') }, + ], + }, + decreaseStakeResult.Memory, + ); + + // get the gateway record + gateway = await handle( + { + Tags: [ + { name: 'Action', value: 'Gateway' }, + { name: 'Address', value: STUB_ADDRESS }, + ], + Timestamp: instantDecreaseStakeTimestamp + 1, + }, + instantDecreaseStakeResult.Memory, + ); + + gatewayData = JSON.parse(gateway.Messages[0].Data); + assert.deepEqual(gatewayData.delegates, []); + assert.deepEqual(gatewayData.totalDelegatedStake, 0); + sharedMemory = instantDecreaseStakeResult.Memory; }); }); }); diff --git a/tests/handlers.test.mjs b/tests/handlers.test.mjs index a20a2ba..763f6f5 100644 --- a/tests/handlers.test.mjs +++ b/tests/handlers.test.mjs @@ -38,7 +38,7 @@ describe('handlers', async () => { const evalIndex = handlersList.indexOf('_eval'); const defaultIndex = handlersList.indexOf('_default'); const pruneIndex = handlersList.indexOf('prune'); - const expectedHandlerCount = 50; // TODO: update this if more handlers are added + const expectedHandlerCount = 51; // TODO: update this if more handlers are added assert.ok(evalIndex === 0); assert.ok(defaultIndex === 1); assert.ok(pruneIndex === 2); From 12d8abc450117909de7a22a0c8c89b790138687e Mon Sep 17 00:00:00 2001 From: Philip Mataras Date: Thu, 17 Oct 2024 19:01:00 -0400 Subject: [PATCH 10/41] uses constants for expedited withdrawal fees --- spec/gar_spec.lua | 45 ++++++++++++++++++++++++--------------------- src/constants.lua | 4 ++-- src/gar.lua | 25 ++++++++++++++----------- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/spec/gar_spec.lua b/spec/gar_spec.lua index 5c122cf..87e1d0b 100644 --- a/spec/gar_spec.lua +++ b/spec/gar_spec.lua @@ -1,3 +1,4 @@ +local constants = require("constants") local gar = require("gar") local utils = require("utils") @@ -1059,8 +1060,8 @@ describe("gar", function() it("should decrease delegated stake with instant withdrawal and apply penalty and remove delegate", function() Balances[ao.id] = 0 - local penaltyAmount = 1000 * 0.80 - local withdrawalAmount = 1000 - penaltyAmount + local expenditedWithdrawalFee = 1000 * 0.80 + local withdrawalAmount = 1000 - expenditedWithdrawalFee GatewayRegistry[stubGatewayAddress] = { operatorStake = gar.getSettings().operators.minStake, totalDelegatedStake = gar.getSettings().delegates.minStake + 1000, @@ -1101,7 +1102,7 @@ describe("gar", function() assert.are.same(result.delegates[stubRandomAddress].delegatedStake, gar.getSettings().delegates.minStake) assert.are.equal(result.totalDelegatedStake, gar.getSettings().delegates.minStake) assert.are.equal(withdrawalAmount, Balances[stubRandomAddress]) - assert.are.equal(penaltyAmount, Balances[ao.id]) + assert.are.equal(expenditedWithdrawalFee, Balances[ao.id]) assert.are.equal( gar.getSettings().delegates.minStake, _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake @@ -1116,9 +1117,9 @@ describe("gar", function() local currentTimestamp = 1000000 local startTimestamp = 1000000 local vaultBalance = 1000 - local expectedPenaltyRate = 0.80 - local expectedPenaltyAmount = vaultBalance * expectedPenaltyRate - local expectedWithdrawalAmount = vaultBalance - expectedPenaltyAmount + local expectedPenaltyRate = constants.MAX_EXPEDITED_WITHDRAWAL_FEE + local expectedexpenditedWithdrawalFee = vaultBalance * expectedPenaltyRate + local expectedWithdrawalAmount = vaultBalance - expectedexpenditedWithdrawalFee Balances[ao.id] = 0 @@ -1165,7 +1166,7 @@ describe("gar", function() assert.are.equal(nil, result.delegate) -- Delegate should be removed after full withdrawal assert.are.equal(0, result.totalDelegatedStake) assert.are.equal(expectedWithdrawalAmount, Balances[stubRandomAddress]) - assert.are.equal(expectedPenaltyAmount, Balances[ao.id]) + assert.are.equal(expectedexpenditedWithdrawalFee, Balances[ao.id]) assert.are.equal(0, _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake) end ) @@ -1180,12 +1181,13 @@ describe("gar", function() local elapsedTime = 15 * 24 * 60 * 60 * 1000 -- Half of 30 days in milliseconds local currentTimestamp = startTimestamp + elapsedTime local vaultBalance = 1000 - local maxPenalty = 0.80 - local minPenalty = 0.05 - local penaltyRate = maxPenalty - - ((maxPenalty - minPenalty) * (elapsedTime / gar.getSettings().delegates.withdrawLengthMs)) - local expectedPenaltyAmount = math.floor(vaultBalance * penaltyRate) - local expectedWithdrawalAmount = vaultBalance - expectedPenaltyAmount + local penaltyRate = constants.MAX_EXPEDITED_WITHDRAWAL_FEE + - ( + (constants.MAX_EXPEDITED_WITHDRAWAL_FEE - constants.MIN_EXPEDITED_WITHDRAWAL_FEE) + * (elapsedTime / gar.getSettings().delegates.withdrawLengthMs) + ) + local expectedexpenditedWithdrawalFee = math.floor(vaultBalance * penaltyRate) + local expectedWithdrawalAmount = vaultBalance - expectedexpenditedWithdrawalFee Balances[ao.id] = 0 _G.GatewayRegistry[stubGatewayAddress] = { @@ -1231,7 +1233,7 @@ describe("gar", function() assert.are.equal(nil, next(result.delegate.vaults)) -- Delegate should have no vaults remaining assert.are.equal(remainingDelegateStakeBalance, result.totalDelegatedStake) assert.are.equal(expectedWithdrawalAmount, Balances[stubRandomAddress]) - assert.are.equal(expectedPenaltyAmount, Balances[ao.id]) + assert.are.equal(expectedexpenditedWithdrawalFee, Balances[ao.id]) assert.are.equal( remainingDelegateStakeBalance, _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake @@ -1248,12 +1250,13 @@ describe("gar", function() local startTimestamp = 500000 local elapsedTime = 29 * 24 * 60 * 60 * 1000 -- Half of 30 days in milliseconds local currentTimestamp = startTimestamp + elapsedTime - local maxPenalty = 0.80 - local minPenalty = 0.05 - local penaltyRate = maxPenalty - - ((maxPenalty - minPenalty) * (elapsedTime / gar.getSettings().delegates.withdrawLengthMs)) - local expectedPenaltyAmount = math.floor(vaultBalance * penaltyRate) - local expectedWithdrawalAmount = vaultBalance - expectedPenaltyAmount + local penaltyRate = constants.MAX_EXPEDITED_WITHDRAWAL_FEE + - ( + (constants.MAX_EXPEDITED_WITHDRAWAL_FEE - constants.MIN_EXPEDITED_WITHDRAWAL_FEE) + * (elapsedTime / gar.getSettings().delegates.withdrawLengthMs) + ) + local expectedexpenditedWithdrawalFee = math.floor(vaultBalance * penaltyRate) + local expectedWithdrawalAmount = vaultBalance - expectedexpenditedWithdrawalFee Balances[ao.id] = 0 _G.GatewayRegistry[stubGatewayAddress] = { @@ -1299,7 +1302,7 @@ describe("gar", function() assert.are.equal(nil, result.delegate) -- Delegate should be removed after full withdrawal assert.are.equal(0, result.totalDelegatedStake) assert.are.equal(expectedWithdrawalAmount, Balances[stubRandomAddress]) - assert.are.equal(expectedPenaltyAmount, Balances[ao.id]) + assert.are.equal(expectedexpenditedWithdrawalFee, Balances[ao.id]) assert.are.equal(0, _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake) end ) diff --git a/src/constants.lua b/src/constants.lua index 474858b..31c0462 100644 --- a/src/constants.lua +++ b/src/constants.lua @@ -7,8 +7,8 @@ constants.oneYearSeconds = 60 * 60 * 24 * 365 constants.thirtyDaysSeconds = 60 * 60 * 24 * 30 constants.defaultundernameLimit = 10 constants.totalTokenSupply = 1000000000 * 1000000 -- 1 billion tokens -constants.MIN_WITHDRAW_PENALTY = 0.20 -constants.MAX_WITHDRAW_PENALTY = 0.80 +constants.MIN_EXPEDITED_WITHDRAWAL_FEE = 0.05 +constants.MAX_EXPEDITED_WITHDRAWAL_FEE = 0.80 -- ARNS constants.DEFAULT_UNDERNAME_COUNT = 10 diff --git a/src/gar.lua b/src/gar.lua index 220507d..ef0c273 100644 --- a/src/gar.lua +++ b/src/gar.lua @@ -1,5 +1,6 @@ -- gar.lua local balances = require("balances") +local constants = require("constants") local utils = require("utils") local gar = {} @@ -376,12 +377,11 @@ function gar.decreaseDelegateStake(gatewayAddress, delegator, qty, currentTimest gateway.totalDelegatedStake = gateway.totalDelegatedStake - qty -- Calculate the penalty amount - local maxPenalty = 0.80 - local penaltyAmount = qty * maxPenalty - local amountToWithdraw = qty - penaltyAmount + local expenditedWithdrawalFee = qty * constants.MAX_EXPEDITED_WITHDRAWAL_FEE + local amountToWithdraw = qty - expenditedWithdrawalFee -- Add penalty to AR.IO protocol balance - balances.increaseBalance(ao.id, penaltyAmount) + balances.increaseBalance(ao.id, expenditedWithdrawalFee) -- Withdraw the remaining tokens to the delegate balances.increaseBalance(delegator, amountToWithdraw) @@ -804,19 +804,22 @@ function gar.instantDelegateWithdrawal(from, gatewayAddress, vaultId, currentTim end -- Calculate the penalty rate based on elapsed time - local maxPenalty = 0.80 - local minPenalty = 0.05 - local penaltyRate = maxPenalty - ((maxPenalty - minPenalty) * (elapsedTime / totalWithdrawalTime)) - penaltyRate = math.max(minPenalty, math.min(maxPenalty, penaltyRate)) -- Ensure penalty is within bounds + local penaltyRate = constants.MAX_EXPEDITED_WITHDRAWAL_FEE + - ( + (constants.MAX_EXPEDITED_WITHDRAWAL_FEE - constants.MIN_EXPEDITED_WITHDRAWAL_FEE) + * (elapsedTime / totalWithdrawalTime) + ) + penaltyRate = + math.max(constants.MIN_EXPEDITED_WITHDRAWAL_FEE, math.min(constants.MAX_EXPEDITED_WITHDRAWAL_FEE, penaltyRate)) -- Ensure penalty is within bounds -- Calculate the penalty amount and the amount to withdraw local vaultBalance = vault.balance - local penaltyAmount = math.floor(vaultBalance * penaltyRate) - local amountToWithdraw = vaultBalance - penaltyAmount + local expenditedWithdrawalFee = math.floor(vaultBalance * penaltyRate) + local amountToWithdraw = vaultBalance - expenditedWithdrawalFee -- Add penalty to AR.IO protocol balance - balances.increaseBalance(ao.id, penaltyAmount) + balances.increaseBalance(ao.id, expenditedWithdrawalFee) balances.increaseBalance(from, amountToWithdraw) -- Remove the vault after withdrawal From 01ea83276f85b3d14a66b814fe9183fb8ae73734 Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 17 Oct 2024 19:40:16 -0400 Subject: [PATCH 11/41] Update src/main.lua Co-authored-by: Dylan Fiedler --- src/main.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.lua b/src/main.lua index f038149..4a56ebb 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1225,9 +1225,7 @@ addEventingHandler( msg.ioEvent:addField("PreviousStake", newStake + quantity) msg.ioEvent:addField("NewStake", newStake) msg.ioEvent:addField("GatewayTotalDelegatedStake", gateway.totalDelegatedStake) - if msg.Tags.Instant == true then - msg.ioEvent:addField("InstantWithdrawal", true) - end + msg.ioEvent:addField("InstantWithdrawal", insantWithdraw) delegateResult = gateway.delegates[from] local newDelegateVaults = delegateResult.vaults if newDelegateVaults ~= nil then From e025126757c704a7c6b87eae80a869a7ca74372e Mon Sep 17 00:00:00 2001 From: Philip Mataras Date: Thu, 17 Oct 2024 19:41:38 -0400 Subject: [PATCH 12/41] fixes check on instant tags --- src/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.lua b/src/main.lua index 4a56ebb..c85ff4d 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1205,7 +1205,7 @@ addEventingHandler( local from = utils.formatAddress(msg.From) local target = utils.formatAddress(msg.Tags.Target or msg.Tags.Address) local quantity = tonumber(msg.Tags.Quantity) - local instantWithdraw = msg.Tags.Instant == true + local instantWithdraw = msg.Tags.Instant and msg.Tags.Instant == "true" msg.ioEvent:addField("TargetFormatted", target) local shouldContinue2, gateway = eventingPcall(msg.ioEvent, function(error) From bda1623c470fd4821adc6325fc2ed31a32809692 Mon Sep 17 00:00:00 2001 From: Philip Mataras Date: Fri, 18 Oct 2024 10:09:34 -0400 Subject: [PATCH 13/41] Fixes broken instant tag and test --- src/gar.lua | 2 +- src/main.lua | 8 ++++++-- tests/gar.test.mjs | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/gar.lua b/src/gar.lua index ef0c273..4ab32e7 100644 --- a/src/gar.lua +++ b/src/gar.lua @@ -371,7 +371,7 @@ function gar.decreaseDelegateStake(gatewayAddress, delegator, qty, currentTimest end -- Instant withdrawal logic with penalty - if instantWithdraw then + if instantWithdraw == true then -- Unlock the tokens from the gateway and delegate gateway.delegates[delegator].delegatedStake = gateway.delegates[delegator].delegatedStake - qty gateway.totalDelegatedStake = gateway.totalDelegatedStake - qty diff --git a/src/main.lua b/src/main.lua index c85ff4d..408c98c 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1187,7 +1187,10 @@ addEventingHandler( "Invalid quantity. Must be integer greater than 0" ) if msg.Tags.Instant ~= nil then - assert(type(msg.Tags.Instant) == "boolean", "Instant must be a boolean value") + assert( + msg.Tags.Instant == "true" or msg.Tags.Instant == "false", + "Instant must be a string with value 'true' or 'false'" + ) end end @@ -1205,7 +1208,8 @@ addEventingHandler( local from = utils.formatAddress(msg.From) local target = utils.formatAddress(msg.Tags.Target or msg.Tags.Address) local quantity = tonumber(msg.Tags.Quantity) - local instantWithdraw = msg.Tags.Instant and msg.Tags.Instant == "true" + -- Convert the string value of Instant to a boolean value + local instantWithdraw = msg.Tags.Instant == "true" msg.ioEvent:addField("TargetFormatted", target) local shouldContinue2, gateway = eventingPcall(msg.ioEvent, function(error) diff --git a/tests/gar.test.mjs b/tests/gar.test.mjs index 705534b..5c7efb2 100644 --- a/tests/gar.test.mjs +++ b/tests/gar.test.mjs @@ -830,6 +830,7 @@ describe('GatewayRegistry', async () => { ); const gatewayData = JSON.parse(gateway.Messages[0].Data); + console.log ("1: Gateway Data Is: ", gatewayData) assert.deepEqual(gatewayData.delegates, { [newStubAddress]: { @@ -854,11 +855,13 @@ describe('GatewayRegistry', async () => { { name: 'Action', value: 'Decrease-Delegate-Stake' }, { name: 'Address', value: STUB_ADDRESS }, { name: 'Quantity', value: '1000000000' }, // 1K IO - { name: 'Instant', value: true }, + { name: 'Instant', value: "true" }, ], }, sharedMemory, ); + + console.log ("2: decrease stake result: ", decreaseStakeResult.Messages[0].Data) // get the updated gateway record const gateway = await handle( @@ -871,7 +874,8 @@ describe('GatewayRegistry', async () => { }, decreaseStakeResult.Memory, ); - const gatewayData = JSON.parse(gateway.Messages[0].Data); + const gatewayData = JSON.parse(gateway.Messages[0].Data); + console.log ("3: gateway result: ", gatewayData) // Assertions assert.deepEqual(gatewayData.delegates, { [newStubAddress]: { From 7afe50eb84e63936135df6b173af1c73d0648630 Mon Sep 17 00:00:00 2001 From: Philip Mataras Date: Fri, 18 Oct 2024 10:56:28 -0400 Subject: [PATCH 14/41] Trying to fix test... --- src/main.lua | 10 ++++++++-- tests/gar.test.mjs | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/main.lua b/src/main.lua index 408c98c..38632fb 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1209,7 +1209,13 @@ addEventingHandler( local target = utils.formatAddress(msg.Tags.Target or msg.Tags.Address) local quantity = tonumber(msg.Tags.Quantity) -- Convert the string value of Instant to a boolean value - local instantWithdraw = msg.Tags.Instant == "true" + local instantWithdraw = false + if msg.Tags.Instant and msg.Tags.Instant == "true" then + instantWithdraw = true + else + instantWithdraw = false + end + msg.ioEvent:addField("TargetFormatted", target) local shouldContinue2, gateway = eventingPcall(msg.ioEvent, function(error) @@ -1229,7 +1235,7 @@ addEventingHandler( msg.ioEvent:addField("PreviousStake", newStake + quantity) msg.ioEvent:addField("NewStake", newStake) msg.ioEvent:addField("GatewayTotalDelegatedStake", gateway.totalDelegatedStake) - msg.ioEvent:addField("InstantWithdrawal", insantWithdraw) + msg.ioEvent:addField("InstantWithdrawal", instantWithdraw) delegateResult = gateway.delegates[from] local newDelegateVaults = delegateResult.vaults if newDelegateVaults ~= nil then diff --git a/tests/gar.test.mjs b/tests/gar.test.mjs index 5c7efb2..4ed8d6f 100644 --- a/tests/gar.test.mjs +++ b/tests/gar.test.mjs @@ -830,7 +830,6 @@ describe('GatewayRegistry', async () => { ); const gatewayData = JSON.parse(gateway.Messages[0].Data); - console.log ("1: Gateway Data Is: ", gatewayData) assert.deepEqual(gatewayData.delegates, { [newStubAddress]: { @@ -843,6 +842,39 @@ describe('GatewayRegistry', async () => { sharedMemory = cancelWithdrawalResult.Memory; }); + it('should not decrease delegate stake with invalid instant withdrawal tag', async () => { + const instantWithdrawalTimestamp = stubbedTimestamp + 1000 * 60 * 15; // 15 minutes after stubbedTimestamp + const decreaseStakeResult = await handle( + { + From: newStubAddress, + Owner: newStubAddress, + Timestamp: instantWithdrawalTimestamp, + Id: ''.padEnd(43, 'x'), + Tags: [ + { name: 'Action', value: 'Decrease-Delegate-Stake' }, + { name: 'Address', value: STUB_ADDRESS }, + { name: 'Quantity', value: '1000000000' }, // 1K IO + { name: 'Instant', value: 'false' }, // FOR SOME REASON THIS WORKS IF YOU PUT 'false' :O + ], + }, + sharedMemory, + ); + + // get the updated gateway record + const gateway = await handle( + { + Tags: [ + { name: 'Action', value: 'Gateway' }, + { name: 'Address', value: STUB_ADDRESS }, + ], + Timestamp: instantWithdrawalTimestamp + 1, + }, + decreaseStakeResult.Memory, + ); + const gatewayData = JSON.parse(gateway.Messages[0].Data); + assert.deepEqual(gatewayData.totalDelegatedStake, 2_000_000_000); + }); + it('should decrease delegate stake with instant withdrawal', async () => { const instantWithdrawalTimestamp = stubbedTimestamp + 1000 * 60 * 15; // 15 minutes after stubbedTimestamp const decreaseStakeResult = await handle( @@ -860,8 +892,6 @@ describe('GatewayRegistry', async () => { }, sharedMemory, ); - - console.log ("2: decrease stake result: ", decreaseStakeResult.Messages[0].Data) // get the updated gateway record const gateway = await handle( @@ -875,7 +905,6 @@ describe('GatewayRegistry', async () => { decreaseStakeResult.Memory, ); const gatewayData = JSON.parse(gateway.Messages[0].Data); - console.log ("3: gateway result: ", gatewayData) // Assertions assert.deepEqual(gatewayData.delegates, { [newStubAddress]: { @@ -948,9 +977,12 @@ describe('GatewayRegistry', async () => { ); gatewayData = JSON.parse(gateway.Messages[0].Data); + console.log ("1: ", gatewayData) assert.deepEqual(gatewayData.delegates, []); assert.deepEqual(gatewayData.totalDelegatedStake, 0); sharedMemory = instantDecreaseStakeResult.Memory; }); + + }); }); From 48c4c1112dbd1b95e5ba302a27b6be8c66755f0c Mon Sep 17 00:00:00 2001 From: Philip Mataras Date: Fri, 18 Oct 2024 11:00:05 -0400 Subject: [PATCH 15/41] updates test --- tests/gar.test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gar.test.mjs b/tests/gar.test.mjs index 4ed8d6f..8282b4c 100644 --- a/tests/gar.test.mjs +++ b/tests/gar.test.mjs @@ -854,7 +854,7 @@ describe('GatewayRegistry', async () => { { name: 'Action', value: 'Decrease-Delegate-Stake' }, { name: 'Address', value: STUB_ADDRESS }, { name: 'Quantity', value: '1000000000' }, // 1K IO - { name: 'Instant', value: 'false' }, // FOR SOME REASON THIS WORKS IF YOU PUT 'false' :O + { name: 'Instant', value: 'RANDOM' }, ], }, sharedMemory, From 05dc364f23d9a33c5cbd144193e17b105f0f38e8 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 18 Oct 2024 12:10:08 -0500 Subject: [PATCH 16/41] chore(main): small tweak to flagging instant --- src/main.lua | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main.lua b/src/main.lua index 38632fb..bde4fbb 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1208,13 +1208,7 @@ addEventingHandler( local from = utils.formatAddress(msg.From) local target = utils.formatAddress(msg.Tags.Target or msg.Tags.Address) local quantity = tonumber(msg.Tags.Quantity) - -- Convert the string value of Instant to a boolean value - local instantWithdraw = false - if msg.Tags.Instant and msg.Tags.Instant == "true" then - instantWithdraw = true - else - instantWithdraw = false - end + local instantWithdraw = msg.Tags.Instant and msg.Tags.Instant == "true" or false msg.ioEvent:addField("TargetFormatted", target) From 6767ff69fcba34572cad05ec0d63430923e1385a Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 18 Oct 2024 12:12:51 -0500 Subject: [PATCH 17/41] chore: run prettier --- tests/gar.test.mjs | 18 ++++++++---------- tests/monitor/monitor.test.mjs | 9 +++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/gar.test.mjs b/tests/gar.test.mjs index 8282b4c..27f6a12 100644 --- a/tests/gar.test.mjs +++ b/tests/gar.test.mjs @@ -830,7 +830,7 @@ describe('GatewayRegistry', async () => { ); const gatewayData = JSON.parse(gateway.Messages[0].Data); - + assert.deepEqual(gatewayData.delegates, { [newStubAddress]: { delegatedStake: 2_000_000_000, @@ -859,7 +859,7 @@ describe('GatewayRegistry', async () => { }, sharedMemory, ); - + // get the updated gateway record const gateway = await handle( { @@ -887,12 +887,12 @@ describe('GatewayRegistry', async () => { { name: 'Action', value: 'Decrease-Delegate-Stake' }, { name: 'Address', value: STUB_ADDRESS }, { name: 'Quantity', value: '1000000000' }, // 1K IO - { name: 'Instant', value: "true" }, + { name: 'Instant', value: 'true' }, ], }, sharedMemory, ); - + // get the updated gateway record const gateway = await handle( { @@ -915,7 +915,6 @@ describe('GatewayRegistry', async () => { }); assert.deepEqual(gatewayData.totalDelegatedStake, 1_000_000_000); sharedMemory = decreaseStakeResult.Memory; - }); it('should allow decrease delegate stake from a gateway followed up with instant withdrawal', async () => { @@ -946,9 +945,10 @@ describe('GatewayRegistry', async () => { }, decreaseStakeResult.Memory, ); - let gatewayData = JSON.parse(gateway.Messages[0].Data); + let gatewayData = JSON.parse(gateway.Messages[0].Data); - const instantDecreaseStakeTimestamp = decreaseStakeTimestamp + 1000 * 60 * 60; // 60 minutes after stubbedTimestamp + const instantDecreaseStakeTimestamp = + decreaseStakeTimestamp + 1000 * 60 * 60; // 60 minutes after stubbedTimestamp const instantDecreaseStakeResult = await handle( { From: newStubAddress, @@ -977,12 +977,10 @@ describe('GatewayRegistry', async () => { ); gatewayData = JSON.parse(gateway.Messages[0].Data); - console.log ("1: ", gatewayData) + console.log('1: ', gatewayData); assert.deepEqual(gatewayData.delegates, []); assert.deepEqual(gatewayData.totalDelegatedStake, 0); sharedMemory = instantDecreaseStakeResult.Memory; }); - - }); }); diff --git a/tests/monitor/monitor.test.mjs b/tests/monitor/monitor.test.mjs index cc98fe2..0e2f570 100644 --- a/tests/monitor/monitor.test.mjs +++ b/tests/monitor/monitor.test.mjs @@ -244,6 +244,7 @@ describe('setup', () => { if (gateway.status === 'leaving') { assert(gateway.totalDelegatedStake === 0); assert(gateway.operatorStake === 0); +<<<<<<< HEAD for (const [vaultId, vault] of Object.entries(gateway.vaults)) { if (vaultId === gateway.gatewayAddress) { assert( @@ -270,6 +271,14 @@ describe('setup', () => { `Vault ${vaultId} on gateway ${gateway.gatewayAddress} has an invalid end timestamp (${vault.endTimestamp})`, ); } +======= + assert( + gateway.vaults[gateway.gatewayAddress].balance >= 0 && + gateway.vaults[gateway.gatewayAddress].balance <= + 50_000_000_000, + `Gateway ${gateway.gatewayAddress} is leaving with invalid amount of IO vaulted against the wallet address. Any stake higher than the minimum staked amount of 50_000_000_000 IO should be vaulted against the message id.`, + ); +>>>>>>> bdce999 (chore: run prettier) } } cursor = nextCursor; From dfcef33c89d61cb60874de611b7382f986be556a Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 18 Oct 2024 12:42:31 -0500 Subject: [PATCH 18/41] chore(test): increase coverage --- docs/save_observations.md | 4 +- spec/gar_spec.lua | 120 +++++++++++++++++++++++++++++++-- spec/vaults_spec.lua | 4 +- src/constants.lua | 2 +- src/epochs.lua | 2 +- src/gar.lua | 44 ++++++------ tests/gar.test.mjs | 2 +- tests/monitor/monitor.test.mjs | 9 --- 8 files changed, 146 insertions(+), 41 deletions(-) diff --git a/docs/save_observations.md b/docs/save_observations.md index 16b390e..e5952f3 100644 --- a/docs/save_observations.md +++ b/docs/save_observations.md @@ -10,13 +10,13 @@ graph TD CheckGateway -- Doesn't Exist --> Error[Throw Error] CheckPrescribedObserver -- Prescribed --> CheckEpoch{Check Epoch Object} CheckPrescribedObserver -- Not Prescribed --> Error - CheckEpoch -- Does not Exist --> CreateEpoch[Create Epoch Object] + CheckEpoch -- not found --> CreateEpoch[Create Epoch Object] CheckEpoch -- Exists --> CheckFailedGateway{Check Failed Gateway} CreateEpoch -- Created --> CheckFailedGateway{Check Failed Gateway} CheckFailedGateway -- Valid --> ProcessFailedGateway{Check existing gateway failures} CheckFailedGateway -- Invalid --> Skip Skip --> UpdateObserverReportTxId[Update Observer Report Tx Id] - ProcessFailedGateway -- Does not Exist --> CreateFailedGateway[Create Failed Gateway Object] + ProcessFailedGateway -- not found --> CreateFailedGateway[Create Failed Gateway Object] ProcessFailedGateway -- Exists --> UpdateFailedGateway[Update Failed Gateway Object] UpdateFailedGateway -- Updated --> UpdateObserverReportTxId[Update Observer Report Tx Id] CreateFailedGateway -- Created --> UpdateObserverReportTxId[Update Observer Report Tx Id] diff --git a/spec/gar_spec.lua b/spec/gar_spec.lua index 87e1d0b..f9ccfd1 100644 --- a/spec/gar_spec.lua +++ b/spec/gar_spec.lua @@ -1109,6 +1109,52 @@ describe("gar", function() ) end) + it("should error if the remaining delegate stake is less than the minimum stake", function() + local delegatedStake = gar.getSettings().delegates.minStake + GatewayRegistry[stubGatewayAddress] = { + operatorStake = gar.getSettings().operators.minStake, + totalDelegatedStake = gar.getSettings().delegates.minStake - 1, + vaults = {}, + delegates = { + [stubRandomAddress] = { + delegatedStake = delegatedStake, + startTimestamp = startTimestamp, + vaults = {}, + }, + }, + startTimestamp = startTimestamp, + stats = { + prescribedEpochCount = 0, + observedEpochCount = 0, + totalEpochCount = 0, + passedEpochCount = 0, + failedEpochCount = 0, + failedConsecutiveEpochs = 0, + passedConsecutiveEpochs = 0, + }, + settings = testSettings, + status = "joined", + observerAddress = stubObserverAddress, + } + + local status, err = pcall( + gar.decreaseDelegateStake, + stubGatewayAddress, + stubRandomAddress, + 1, + startTimestamp, + stubMessageId + ) + assert.is_false(status) + assert.is_not_nil(err) + assert.matches( + "Remaining delegated stake must be greater than the minimum delegated stake. Adjust the amount or withdraw all stake.", + err + ) + end) + end) + + describe("instantDelegateWithdrawal", function() it( "should successfully convert a standard delegate withdraw to instant with maximum penalty and remove delegate", function() @@ -1306,6 +1352,72 @@ describe("gar", function() assert.are.equal(0, _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake) end ) + + it("should error if the vault is not found", function() + local vaultId = "vault_id_1" + local vaultBalance = 1000 + local startTimestamp = 500000 + local currentTimestamp = startTimestamp + 1000 + + _G.GatewayRegistry[stubGatewayAddress] = { + operatorStake = gar.getSettings().operators.minStake, + totalDelegatedStake = 0, + vaults = {}, + delegates = { + [stubRandomAddress] = { + delegatedStake = 0, + startTimestamp = startTimestamp, + vaults = { + [vaultId] = { + balance = vaultBalance, + startTimestamp = startTimestamp, + }, + }, + }, + }, + startTimestamp = startTimestamp, + stats = { + prescribedEpochCount = 0, + observedEpochCount = 0, + totalEpochCount = 0, + passedEpochCount = 0, + failedEpochCount = 0, + failedConsecutiveEpochs = 0, + passedConsecutiveEpochs = 0, + }, + settings = testSettings, + status = "joined", + observerAddress = stubObserverAddress, + } + + local status, err = pcall( + gar.instantDelegateWithdrawal, + stubRandomAddress, + stubGatewayAddress, + "non-existent-vault-id", + currentTimestamp + ) + assert.is_false(status) + assert.is_not_nil(err) + assert.matches("Vault not found", err) + end) + + it("should error if the gateway is not found", function() + local vaultId = "vault_id_1" + local startTimestamp = 500000 + local currentTimestamp = startTimestamp + 1000 + + local status, err = pcall( + gar.instantDelegateWithdrawal, + stubRandomAddress, + "non-existent-gateway-address", + vaultId, + currentTimestamp + ) + assert.is_false(status) + assert.is_not_nil(err) + assert.matches("Gateway not found", err) + end) end) describe("slashOperatorStake", function() @@ -1538,7 +1650,7 @@ describe("gar", function() }, }, _G.GatewayRegistry[stubGatewayAddress].delegates[stubRandomAddress]) end) - it("should not cancel a withdrawal if the delegate does not exist", function() + it("should not cancel a withdrawal if the delegate not found", function() _G.GatewayRegistry[stubGatewayAddress] = testGateway _G.GatewayRegistry[stubGatewayAddress].delegates[stubRandomAddress] = nil _G.GatewayRegistry[stubGatewayAddress].settings.allowDelegatedStaking = true @@ -1550,9 +1662,9 @@ describe("gar", function() ) assert.is_false(status) assert.is_not_nil(err) - assert.matches("Delegate does not exist", err) + assert.matches("Delegate not found", err) end) - it("should not cancel a withdrawal if the withdrawal does not exist", function() + it("should not cancel a withdrawal if the withdrawal not found", function() _G.GatewayRegistry[stubGatewayAddress] = testGateway _G.GatewayRegistry[stubGatewayAddress].delegates[stubRandomAddress] = { delegatedStake = 0, @@ -1568,7 +1680,7 @@ describe("gar", function() ) assert.is_false(status) assert.is_not_nil(err) - assert.matches("Vault does not exist", err) + assert.matches("Vault not found", err) end) it("should not cancel a withdrawal if the gateway is leaving", function() _G.GatewayRegistry[stubGatewayAddress] = testGateway diff --git a/spec/vaults_spec.lua b/spec/vaults_spec.lua index 18183d9..0ca42cc 100644 --- a/spec/vaults_spec.lua +++ b/spec/vaults_spec.lua @@ -132,7 +132,7 @@ describe("vaults", function() assert.match("This vault has ended.", result) end) - it("should throw an error if the vault does not exist", function() + it("should throw an error if the vault not found", function() local vaultOwner = "test-this-is-valid-arweave-wallet-address-1" local vaultId = "msgId" local status, result = pcall(vaults.increaseVault, vaultOwner, 100, vaultId, startTimestamp) @@ -187,7 +187,7 @@ describe("vaults", function() ) end) - it("should throw an error if the vault does not exist", function() + it("should throw an error if the vault not found", function() local vaultOwner = "test-this-is-valid-arweave-wallet-address-1" local vaultId = "msgId" local status, result = pcall(vaults.extendVault, vaultOwner, 100, startTimestamp, vaultId) diff --git a/src/constants.lua b/src/constants.lua index 31c0462..9066ac5 100644 --- a/src/constants.lua +++ b/src/constants.lua @@ -15,7 +15,7 @@ constants.DEFAULT_UNDERNAME_COUNT = 10 constants.DEADLINE_DURATION_MS = 60 * 60 * 1000 -- One hour of miliseconds constants.PERMABUY_LEASE_FEE_LENGTH = 20 -- 20 years constants.ANNUAL_PERCENTAGE_FEE = 0.2 -- 20% -constants.ARNS_NAME_DOES_NOT_EXIST_MESSAGE = "Name does not exist in the ArNS Registry!" +constants.ARNS_NAME_DOES_NOT_EXIST_MESSAGE = "Name not found in the ArNS Registry!" constants.UNDERNAME_LEASE_FEE_PERCENTAGE = 0.001 constants.UNDERNAME_PERMABUY_FEE_PERCENTAGE = 0.005 constants.oneYearMs = 31536000 * 1000 diff --git a/src/epochs.lua b/src/epochs.lua index 20d452b..a2c999d 100644 --- a/src/epochs.lua +++ b/src/epochs.lua @@ -364,7 +364,7 @@ function epochs.saveObservations(observerAddress, reportTxId, failedGatewayAddre local observingGateway = gar.getGateway(observer.gatewayAddress) if observingGateway == nil then - error("The associated gateway does not exist in the registry.") + error("The associated gateway not found in the registry.") end local epoch = epochs.getEpoch(epochIndex) diff --git a/src/gar.lua b/src/gar.lua index 4ab32e7..37d6959 100644 --- a/src/gar.lua +++ b/src/gar.lua @@ -78,7 +78,7 @@ function gar.leaveNetwork(from, currentTimestamp, msgId) local gateway = gar.getGateway(from) if not gateway then - error("Gateway does not exist in the network") + error("Gateway not found") end if not gar.isGatewayEligibleToLeave(gateway, currentTimestamp) then @@ -142,8 +142,8 @@ function gar.increaseOperatorStake(from, qty) local gateway = gar.getGateway(from) - if gateway == nil then - error("Gateway does not exist") + if not gateway then + error("Gateway not found") end if gateway.status == "leaving" then @@ -167,8 +167,8 @@ function gar.decreaseOperatorStake(from, qty, currentTimestamp, msgId) local gateway = gar.getGateway(from) - if gateway == nil then - error("Gateway does not exist") + if not gateway then + error("Gateway not found") end if gateway.status == "leaving" then @@ -200,7 +200,7 @@ function gar.updateGatewaySettings(from, updatedSettings, updatedServices, obser local gateway = gar.getGateway(from) if not gateway then - error("Gateway does not exist") + error("Gateway not found") end if gateway.status == "leaving" then @@ -284,8 +284,8 @@ function gar.delegateStake(from, target, qty, currentTimestamp) assert(type(from) == "string", "From is required and must be a string") local gateway = gar.getGateway(target) - if gateway == nil then - error("Gateway does not exist") + if not gateway then + error("Gateway not found") end -- don't allow delegating to yourself @@ -353,7 +353,7 @@ function gar.decreaseDelegateStake(gatewayAddress, delegator, qty, currentTimest local gateway = gar.getGateway(gatewayAddress) if not gateway then - error("Gateway does not exist") + error("Gateway not found") end if gateway.status == "leaving" then error("Gateway is leaving the network and withdraw more stake.") @@ -367,7 +367,9 @@ function gar.decreaseDelegateStake(gatewayAddress, delegator, qty, currentTimest local requiredMinimumStake = gateway.settings.minDelegatedStake local maxAllowedToWithdraw = existingStake - requiredMinimumStake if maxAllowedToWithdraw < qty and qty ~= existingStake then - error("Remaining delegated stake must be greater than the minimum delegated stake amount.") + error( + "Remaining delegated stake must be greater than the minimum delegated stake. Adjust the amount or withdraw all stake." + ) end -- Instant withdrawal logic with penalty @@ -413,8 +415,8 @@ function gar.isGatewayLeaving(gateway) end function gar.isGatewayEligibleToLeave(gateway, timestamp) - if gateway == nil then - error("Gateway does not exist") + if not gateway then + error("Gateway not found") end local isJoined = gar.isGatewayJoined(gateway, timestamp) return isJoined @@ -597,7 +599,7 @@ end function gar.updateGatewayStats(address, stats) local gateway = gar.getGateway(address) if gateway == nil then - error("Gateway does not exist") + error("Gateway not found") end assert(stats.prescribedEpochCount, "prescribedEpochCount is required") @@ -616,7 +618,7 @@ function gar.updateGatewayWeights(weightedGateway) local address = weightedGateway.gatewayAddress local gateway = gar.getGateway(address) if gateway == nil then - error("Gateway does not exist") + error("Gateway not found") end assert(weightedGateway.stakeWeight, "stakeWeight is required") @@ -717,7 +719,7 @@ function gar.slashOperatorStake(address, slashAmount) local gateway = gar.getGateway(address) if gateway == nil then - error("Gateway does not exist") + error("Gateway not found") end local garSettings = gar.getSettings() if garSettings == nil then @@ -745,7 +747,7 @@ end function gar.cancelDelegateWithdrawal(from, gatewayAddress, vaultId) local gateway = gar.getGateway(gatewayAddress) if gateway == nil then - error("Gateway does not exist") + error("Gateway not found") end if gateway.status == "leaving" then @@ -754,12 +756,12 @@ function gar.cancelDelegateWithdrawal(from, gatewayAddress, vaultId) local delegate = gateway.delegates[from] if delegate == nil then - error("Delegate does not exist") + error("Delegate not found") end local vault = delegate.vaults[vaultId] if vault == nil then - error("Vault does not exist") + error("Vault not found") end -- confirm the gateway still allow staking @@ -781,17 +783,17 @@ end function gar.instantDelegateWithdrawal(from, gatewayAddress, vaultId, currentTimestamp) local gateway = gar.getGateway(gatewayAddress) if gateway == nil then - error("Gateway does not exist") + error("Gateway not found") end local delegate = gateway.delegates[from] if delegate == nil then - error("Delegate does not exist") + error("Delegate not found") end local vault = delegate.vaults[vaultId] if vault == nil then - error("Vault does not exist") + error("Vault not found") end -- Calculate elapsed time since the withdrawal started diff --git a/tests/gar.test.mjs b/tests/gar.test.mjs index 27f6a12..36f2fa7 100644 --- a/tests/gar.test.mjs +++ b/tests/gar.test.mjs @@ -275,7 +275,7 @@ describe('GatewayRegistry', async () => { ); const gatewayData = JSON.parse(gateway.Messages[0].Data); - // Assert gateway data does not exist + // Assert gateway data not found assert.equal(gatewayData, null); }); }); diff --git a/tests/monitor/monitor.test.mjs b/tests/monitor/monitor.test.mjs index 0e2f570..cc98fe2 100644 --- a/tests/monitor/monitor.test.mjs +++ b/tests/monitor/monitor.test.mjs @@ -244,7 +244,6 @@ describe('setup', () => { if (gateway.status === 'leaving') { assert(gateway.totalDelegatedStake === 0); assert(gateway.operatorStake === 0); -<<<<<<< HEAD for (const [vaultId, vault] of Object.entries(gateway.vaults)) { if (vaultId === gateway.gatewayAddress) { assert( @@ -271,14 +270,6 @@ describe('setup', () => { `Vault ${vaultId} on gateway ${gateway.gatewayAddress} has an invalid end timestamp (${vault.endTimestamp})`, ); } -======= - assert( - gateway.vaults[gateway.gatewayAddress].balance >= 0 && - gateway.vaults[gateway.gatewayAddress].balance <= - 50_000_000_000, - `Gateway ${gateway.gatewayAddress} is leaving with invalid amount of IO vaulted against the wallet address. Any stake higher than the minimum staked amount of 50_000_000_000 IO should be vaulted against the message id.`, - ); ->>>>>>> bdce999 (chore: run prettier) } } cursor = nextCursor; From ec28cfaf58922550345a65358182444e4eabe5b4 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 18 Oct 2024 12:53:08 -0500 Subject: [PATCH 19/41] chore(test): more coverage --- spec/gar_spec.lua | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/main.lua | 23 +++++++++++++---------- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/spec/gar_spec.lua b/spec/gar_spec.lua index f9ccfd1..a791f1c 100644 --- a/spec/gar_spec.lua +++ b/spec/gar_spec.lua @@ -927,6 +927,21 @@ describe("gar", function() assert.is_not_nil(err) assert.matches("Gateway is leaving the network and cannot be updated", err) end) + + it("should not update gateway settings if the gateway is not found", function() + local status, err = pcall( + gar.updateGatewaySettings, + stubGatewayAddress, + updatedSettings, + nil, + stubObserverAddress, + startTimestamp, + stubMessageId + ) + assert.is_false(status) + assert.is_not_nil(err) + assert.matches("Gateway not found", err) + end) end) describe("delegateStake", function() @@ -1707,6 +1722,36 @@ describe("gar", function() assert.is_not_nil(err) assert.matches("Gateway is leaving the network and cannot cancel withdrawals.", err) end) + it("should not cancel a withdrawal if the withdrawal is not found", function() + _G.GatewayRegistry[stubGatewayAddress] = testGateway + _G.GatewayRegistry[stubGatewayAddress].status = "joined" + _G.GatewayRegistry[stubGatewayAddress].delegates[stubRandomAddress] = { + delegatedStake = 0, + vaults = {}, + startTimestamp = 0, + } + local status, err = pcall( + gar.cancelDelegateWithdrawal, + stubRandomAddress, + stubGatewayAddress, + "some-previous-withdrawal-id" + ) + assert.is_false(status) + assert.is_not_nil(err) + assert.matches("Vault not found", err) + end) + it("should not cancel a withdrawal if the gateway is not found", function() + _G.GatewayRegistry[stubGatewayAddress] = nil + local status, err = pcall( + gar.cancelDelegateWithdrawal, + stubRandomAddress, + stubGatewayAddress, + "some-previous-withdrawal-id" + ) + assert.is_false(status) + assert.is_not_nil(err) + assert.matches("Gateway not found", err) + end) end) describe("getActiveGatewaysBeforeTimestamp", function() diff --git a/src/main.lua b/src/main.lua index bde4fbb..52a4043 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1209,8 +1209,11 @@ addEventingHandler( local target = utils.formatAddress(msg.Tags.Target or msg.Tags.Address) local quantity = tonumber(msg.Tags.Quantity) local instantWithdraw = msg.Tags.Instant and msg.Tags.Instant == "true" or false + local timestamp = tonumber(msg.Timestamp) + local messageId = msg.Id - msg.ioEvent:addField("TargetFormatted", target) + msg.ioEvent:addField("Target-Formatted", target) + msg.ioEvent:addField("Quantity", quantity) local shouldContinue2, gateway = eventingPcall(msg.ioEvent, function(error) ao.send({ @@ -1218,7 +1221,7 @@ addEventingHandler( Tags = { Action = "Invalid-Decrease-Delegate-Stake-Notice", Error = "Invalid-Decrease-Delegate-Stake" }, Data = tostring(error), }) - end, gar.decreaseDelegateStake, target, from, quantity, msg.Timestamp, msg.Id, instantWithdraw) + end, gar.decreaseDelegateStake, target, from, quantity, timestamp, messageId, instantWithdraw) if not shouldContinue2 then return end @@ -1226,10 +1229,10 @@ addEventingHandler( local delegateResult = {} if gateway ~= nil then local newStake = gateway.delegates[from].delegatedStake - msg.ioEvent:addField("PreviousStake", newStake + quantity) - msg.ioEvent:addField("NewStake", newStake) - msg.ioEvent:addField("GatewayTotalDelegatedStake", gateway.totalDelegatedStake) - msg.ioEvent:addField("InstantWithdrawal", instantWithdraw) + msg.ioEvent:addField("Previous-Stake", newStake + quantity) + msg.ioEvent:addField("New-Stake", newStake) + msg.ioEvent:addField("Gateway-Total-Delegated-Stake", gateway.totalDelegatedStake) + msg.ioEvent:addField("Instant-Withdrawal", instantWithdraw) delegateResult = gateway.delegates[from] local newDelegateVaults = delegateResult.vaults if newDelegateVaults ~= nil then @@ -1237,16 +1240,16 @@ addEventingHandler( local newDelegateVault = newDelegateVaults[msg.Id] if newDelegateVault ~= nil then msg.ioEvent:addField("Vault-Id", msg.Id) - msg.ioEvent:addField("VaultBalance", newDelegateVault.balance) - msg.ioEvent:addField("VaultStartTimestamp", newDelegateVault.startTimestamp) - msg.ioEvent:addField("VaultEndTimestamp", newDelegateVault.endTimestamp) + msg.ioEvent:addField("Vault-Balance", newDelegateVault.balance) + msg.ioEvent:addField("Vault-Start-Timestamp", newDelegateVault.startTimestamp) + msg.ioEvent:addField("Vault-End-Timestamp", newDelegateVault.endTimestamp) end end end ao.send({ Target = from, - Tags = { Action = "Decrease-Delegate-Stake-Notice", Adddress = target, Quantity = msg.Tags.Quantity }, + Tags = { Action = "Decrease-Delegate-Stake-Notice", Address = target, Quantity = quantity }, Data = json.encode(delegateResult), }) end From 7f67f2738a905ed985017a654e4198c602a0e7d5 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 18 Oct 2024 12:55:05 -0500 Subject: [PATCH 20/41] chore(montior): tweak cron for tests --- .github/workflows/monitor.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/monitor.yaml b/.github/workflows/monitor.yaml index 450d98f..b6a21d9 100644 --- a/.github/workflows/monitor.yaml +++ b/.github/workflows/monitor.yaml @@ -3,7 +3,7 @@ name: IO Process Status on: workflow_dispatch: schedule: - - cron: '0 1-5,8-23 * * *' # Run every hour except 6AM UTC + - cron: '0 0-5,8-23 * * *' # Run every hour except 6AM UTC jobs: monitor: From 3691d2d01211d0bb24a0a71a8b016a0df76ee23c Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 18 Oct 2024 14:05:19 -0500 Subject: [PATCH 21/41] chore(test): add tests for getTokenCost --- spec/arns_spec.lua | 50 ++++++++++++++++++++++++++++++++++++++++++++++ src/arns.lua | 9 +++++---- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/spec/arns_spec.lua b/spec/arns_spec.lua index e0b4611..6d720a8 100644 --- a/spec/arns_spec.lua +++ b/spec/arns_spec.lua @@ -424,6 +424,56 @@ describe("arns", function() end) end + describe("getTokenCost", function() + it("should return the correct token cost for a lease", function() + local baseFee = 500000000 + local years = 2 + local demandFactor = 1.0 + local expectedCost = ((years * baseFee * 0.20) + baseFee) * demandFactor + local intendedAction = { + intent = "Buy-Record", + purchaseType = "lease", + years = 2, + name = "test-name", + } + assert.are.equal(expectedCost, arns.getTokenCost(intendedAction)) + end) + it("should return the correct token cost for a permabuy", function() + local baseFee = 500000000 + local demandFactor = 1.0 + local expectedCost = ((baseFee * 0.2 * 20) + baseFee) * demandFactor + local intendedAction = { + intent = "Buy-Record", + purchaseType = "permabuy", + name = "test-name", + } + assert.are.equal(expectedCost, arns.getTokenCost(intendedAction)) + end) + it("should return the correct token cost for an undername", function() + _G.NameRegistry.records["test-name"] = { + endTimestamp = constants.oneYearMs, + processId = testProcessId, + purchasePrice = 600000000, + startTimestamp = 0, + type = "lease", + undernameLimit = 10, + } + local baseFee = 500000000 + local undernamePercentageFee = 0.001 + local increaseQty = 5 + local demandFactor = 1.0 + local yearsRemaining = 0.5 + local expectedCost = baseFee * increaseQty * undernamePercentageFee * yearsRemaining * demandFactor + local intendedAction = { + intent = "Increase-Undername-Limit", + quantity = 5, + name = "test-name", + currentTimestamp = constants.oneYearMs / 2, + } + assert.are.equal(expectedCost, arns.getTokenCost(intendedAction)) + end) + end) + describe("pruneRecords", function() it("should prune records", function() local currentTimestamp = 1000000000 diff --git a/src/arns.lua b/src/arns.lua index ad64add..76c68ae 100644 --- a/src/arns.lua +++ b/src/arns.lua @@ -249,8 +249,7 @@ function arns.calculateLeaseFee(baseFee, years, demandFactor) end function arns.calculateAnnualRenewalFee(baseFee, years) - local nameAnnualRegistrationFee = baseFee * constants.ANNUAL_PERCENTAGE_FEE - local totalAnnualRenewalCost = nameAnnualRegistrationFee * years + local totalAnnualRenewalCost = baseFee * constants.ANNUAL_PERCENTAGE_FEE * years return math.floor(totalAnnualRenewalCost) end @@ -369,9 +368,11 @@ function arns.getTokenCost(intendedAction) local purchaseType = intendedAction.purchaseType local years = intendedAction.years local name = intendedAction.name - assert(purchaseType == "lease" or purchaseType == "permabuy", "PurchaseType is invalid.") - assert(years >= 1 and years <= 5, "Years is invalid. Must be an integer between 1 and 5") assert(type(name) == "string", "Name is required and must be a string.") + assert(purchaseType == "lease" or purchaseType == "permabuy", "PurchaseType is invalid.") + if purchaseType == "lease" then + assert(years >= 1 and years <= 5, "Years is invalid. Must be an integer between 1 and 5") + end local baseFee = demand.getFees()[#name] tokenCost = arns.calculateRegistrationFee(purchaseType, baseFee, years, demand.getDemandFactor()) elseif intendedAction.intent == "Extend-Lease" then From 77781df61d6b82af74c8fdea4e434811ea703314 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 18 Oct 2024 14:07:14 -0500 Subject: [PATCH 22/41] chore(monitor): added new handler for `Instant-Delegate-Withdrawl` and need to update monitor tests --- tests/monitor/monitor.test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/monitor/monitor.test.mjs b/tests/monitor/monitor.test.mjs index cc98fe2..06fcb66 100644 --- a/tests/monitor/monitor.test.mjs +++ b/tests/monitor/monitor.test.mjs @@ -33,7 +33,7 @@ describe('setup', () => { describe('handlers', () => { it('should always have correct number of handlers', async () => { - const expectedHandlerCount = 52; // TODO: update this if more handlers are added + const expectedHandlerCount = 53; // TODO: update this if more handlers are added const { Handlers: handlersList } = await io.getInfo(); /** * There are two security handlers before _eval and _default, so count is 52 From c1e270f808b4d5da536a70d46de42ddafb0a0e00 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 18 Oct 2024 14:15:09 -0500 Subject: [PATCH 23/41] chore(test): use different demand factors for tests --- spec/arns_spec.lua | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/spec/arns_spec.lua b/spec/arns_spec.lua index 6d720a8..b798daf 100644 --- a/spec/arns_spec.lua +++ b/spec/arns_spec.lua @@ -21,6 +21,7 @@ describe("arns", function() [testAddressArweave] = startBalance, [testAddressEth] = startBalance, } + _G.DemandFactor.currentDemandFactor = 1.0 end) for addressType, testAddress in pairs(testAddresses) do @@ -428,25 +429,27 @@ describe("arns", function() it("should return the correct token cost for a lease", function() local baseFee = 500000000 local years = 2 - local demandFactor = 1.0 - local expectedCost = ((years * baseFee * 0.20) + baseFee) * demandFactor + local demandFactor = 0.974 + local expectedCost = math.floor((years * baseFee * 0.20) + baseFee) * demandFactor local intendedAction = { intent = "Buy-Record", purchaseType = "lease", years = 2, name = "test-name", } + _G.DemandFactor.currentDemandFactor = demandFactor assert.are.equal(expectedCost, arns.getTokenCost(intendedAction)) end) it("should return the correct token cost for a permabuy", function() local baseFee = 500000000 - local demandFactor = 1.0 - local expectedCost = ((baseFee * 0.2 * 20) + baseFee) * demandFactor + local demandFactor = 1.052 + local expectedCost = math.floor((baseFee * 0.2 * 20) + baseFee) * demandFactor local intendedAction = { intent = "Buy-Record", purchaseType = "permabuy", name = "test-name", } + _G.DemandFactor.currentDemandFactor = demandFactor assert.are.equal(expectedCost, arns.getTokenCost(intendedAction)) end) it("should return the correct token cost for an undername", function() @@ -461,15 +464,17 @@ describe("arns", function() local baseFee = 500000000 local undernamePercentageFee = 0.001 local increaseQty = 5 - local demandFactor = 1.0 + local demandFactor = 0.60137 local yearsRemaining = 0.5 - local expectedCost = baseFee * increaseQty * undernamePercentageFee * yearsRemaining * demandFactor + local expectedCost = + math.floor(baseFee * increaseQty * undernamePercentageFee * yearsRemaining * demandFactor) local intendedAction = { intent = "Increase-Undername-Limit", quantity = 5, name = "test-name", currentTimestamp = constants.oneYearMs / 2, } + _G.DemandFactor.currentDemandFactor = demandFactor assert.are.equal(expectedCost, arns.getTokenCost(intendedAction)) end) end) From c0198094d3d9868208e6cf9e720028cb0ca16ed2 Mon Sep 17 00:00:00 2001 From: Ariel Melendez Date: Fri, 18 Oct 2024 11:56:34 -0700 Subject: [PATCH 24/41] feat(events): track supply changes more closely PE-6796 --- spec/gar_spec.lua | 5 +- src/gar.lua | 7 ++ src/main.lua | 189 +++++++++++++++++++++++++++++++--------- src/tick.lua | 6 +- src/vaults.lua | 12 ++- tests/arns.test.mjs | 6 ++ tests/handlers.test.mjs | 4 +- 7 files changed, 177 insertions(+), 52 deletions(-) diff --git a/spec/gar_spec.lua b/spec/gar_spec.lua index a791f1c..0dd0738 100644 --- a/spec/gar_spec.lua +++ b/spec/gar_spec.lua @@ -1557,12 +1557,15 @@ describe("gar", function() local protocolBalanceBefore = _G.Balances[ao.id] or 0 local status, result = pcall(gar.pruneGateways, currentTimestamp, msgId) assert.is_true(status) + local expectedSlashedStake = math.floor(gar.getSettings().operators.minStake * 0.2) assert.are.same({ prunedGateways = { "address1" }, slashedGateways = { "address3" }, + stakeSlashed = expectedSlashedStake, + delegateStakeReturned = 0, + gatewayStakeReturned = 0, }, result) - local expectedSlashedStake = math.floor(gar.getSettings().operators.minStake * 0.2) local expectedRemainingStake = math.floor(gar.getSettings().operators.minStake * 0.8) + 10000 assert.is_nil(GatewayRegistry["address1"]) -- removed assert.is_not_nil(GatewayRegistry["address2"]) -- not removed diff --git a/src/gar.lua b/src/gar.lua index 37d6959..ed80f6e 100644 --- a/src/gar.lua +++ b/src/gar.lua @@ -125,6 +125,7 @@ function gar.leaveNetwork(from, currentTimestamp, msgId) } -- Reduce gateway stake and set this delegate stake to 0 + -- TODO: It's an invariant if totalDelegatedStake isn't 0 at the end of this loop gateway.totalDelegatedStake = gateway.totalDelegatedStake - delegate.delegatedStake gateway.delegates[address].delegatedStake = 0 end @@ -654,6 +655,9 @@ function gar.pruneGateways(currentTimestamp, msgId) local result = { prunedGateways = {}, slashedGateways = {}, + gatewayStakeReturned = 0, + delegateStakeReturned = 0, + stakeSlashed = 0, } if next(gateways) == nil then @@ -667,6 +671,7 @@ function gar.pruneGateways(currentTimestamp, msgId) for vaultId, vault in pairs(gateway.vaults) do if vault.endTimestamp <= currentTimestamp then balances.increaseBalance(address, vault.balance) + result.gatewayStakeReturned = result.gatewayStakeReturned + vault.balance gateway.vaults[vaultId] = nil end end @@ -675,6 +680,7 @@ function gar.pruneGateways(currentTimestamp, msgId) for vaultId, vault in pairs(delegate.vaults) do if vault.endTimestamp <= currentTimestamp then balances.increaseBalance(delegateAddress, vault.balance) + result.delegateStakeReturned = result.delegateStakeReturned + vault.balance delegate.vaults[vaultId] = nil end end @@ -701,6 +707,7 @@ function gar.pruneGateways(currentTimestamp, msgId) gar.slashOperatorStake(address, slashAmount) gar.leaveNetwork(address, currentTimestamp, msgId) table.insert(result.slashedGateways, address) + result.stakeSlashed = result.stakeSlashed + slashAmount else if gateway.status == "leaving" and gateway.endTimestamp <= currentTimestamp then -- if the timestamp is after gateway end timestamp, mark the gateway as nil diff --git a/src/main.lua b/src/main.lua index 52a4043..3591c6e 100644 --- a/src/main.lua +++ b/src/main.lua @@ -197,28 +197,60 @@ end, function(msg) msg.ioEvent:addField("Pruned-Records-Count", prunedRecordsCount) msg.ioEvent:addField("Records-Count", utils.lengthOfTable(NameRegistry.records)) end + local prunedVaultsCount = utils.lengthOfTable(resultOrError.prunedVaults or {}) + if prunedVaultsCount > 0 then + msg.ioEvent:addField("Pruned-Vaults", resultOrError.prunedVaults) + msg.ioEvent:addField("Pruned-Vaults-Count", prunedVaultsCount) + for _, vault in pairs(resultOrError.prunedVaults) do + lastKnownLockedSupply = lastKnownLockedSupply - vault.balance + lastKnownCirculatingSupply = lastKnownCirculatingSupply + vault.balance + end + end local prunedEpochsCount = utils.lengthOfTable(resultOrError.prunedEpochs or {}) if prunedEpochsCount > 0 then msg.ioEvent:addField("Pruned-Epochs", resultOrError.prunedEpochs) msg.ioEvent:addField("Pruned-Epochs-Count", prunedEpochsCount) end - local prunedGatewaysCount = utils.lengthOfTable(resultOrError.prunedGateways or {}) + local pruneGatewayResults = resultOrError.pruneGatewayResults or {} + lastKnownCirculatingSupply = lastKnownCirculatingSupply + + (pruneGatewayResults.delegateStakeReturned or 0) + + (pruneGatewayResults.gatewayStakeReturned or 0) + lastKnownStakedSupply = lastKnownStakedSupply - (pruneGatewayResults.stakeSlashed or 0) + + local prunedGateways = pruneGatewayResults.prunedGateways or {} + local prunedGatewaysCount = utils.lengthOfTable(prunedGateways) if prunedGatewaysCount > 0 then - msg.ioEvent:addField("Pruned-Gateways", resultOrError.prunedGateways) + msg.ioEvent:addField("Pruned-Gateways", prunedGateways) msg.ioEvent:addField("Pruned-Gateways-Count", prunedGatewaysCount) local gwStats = gatewayStats() msg.ioEvent:addField("Joined-Gateways-Count", gwStats.joined) msg.ioEvent:addField("Leaving-Gateways-Count", gwStats.leaving) end - local slashedGatewaysCount = utils.lengthOfTable(resultOrError.slashedGateways or {}) + local slashedGateways = pruneGatewayResults.slashedGateways or {} + local slashedGatewaysCount = utils.lengthOfTable(slashedGateways or {}) if slashedGatewaysCount > 0 then - msg.ioEvent:addField("Slashed-Gateways", resultOrError.slashedGateways) + msg.ioEvent:addField("Slashed-Gateways", slashedGateways) msg.ioEvent:addField("Slashed-Gateways-Count", slashedGatewaysCount) end end + msg.ioEvent:addField( + "Total-Supply", + lastKnownCirculatingSupply + + lastKnownLockedSupply + + lastKnownStakedSupply + + lastKnownDelegatedSupply + + lastKnownWithdrawSupply + ) + msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) + msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + msg.ioEvent:addField("Delegated-Supply", lastKnownDelegatedSupply) + msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) + msg.ioEvent:addField("Protocol-Balance", Balances[Protocol]) + return status end) @@ -353,11 +385,16 @@ addEventingHandler(ActionMap.CreateVault, utils.hasMatchingTag("Action", ActionM if vault ~= nil then msg.ioEvent:addField("Vault-Id", msgId) - msg.ioEvent:addField("VaultBalance", vault.balance) - msg.ioEvent:addField("VaultStartTimestamp", vault.startTimestamp) - msg.ioEvent:addField("VaultEndTimestamp", vault.endTimestamp) + msg.ioEvent:addField("Vault-Balance", vault.balance) + msg.ioEvent:addField("Vault-Start-Timestamp", vault.startTimestamp) + msg.ioEvent:addField("Vault-End-Timestamp", vault.endTimestamp) end + lastKnownLockedSupply = lastKnownLockedSupply + quantity + lastKnownCirculatingSupply = lastKnownCirculatingSupply - quantity + msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) + msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + ao.send({ Target = from, Tags = { @@ -381,14 +418,14 @@ addEventingHandler(ActionMap.VaultedTransfer, utils.hasMatchingTag("Action", Act ) end - local inputStatus, inputResult = pcall(checkAssertions) - - if not inputStatus then + local shouldContinue = eventingPcall(msg.ioEvent, function(error) ao.send({ Target = msg.From, Tags = { Action = "Invalid-Vaulted-Transfer-Notice", Error = "Bad-Input" }, - Data = tostring(inputResult), + Data = tostring(error), }) + end, checkAssertions) + if not shouldContinue then return end @@ -399,32 +436,45 @@ addEventingHandler(ActionMap.VaultedTransfer, utils.hasMatchingTag("Action", Act local timestamp = tonumber(msg.Timestamp) local msgId = msg.Id - local result, err = vaults.vaultedTransfer(from, recipient, quantity, lockLengthMs, timestamp, msgId) - print("Created vault" .. json.encode(Vaults[recipient])) - if err then + local shouldContinue2, vault = eventingPcall(msg.ioEvent, function(error) ao.send({ Target = msg.From, Tags = { Action = "Invalid-Vaulted-Transfer", Error = "Invalid-Vaulted-Transfer" }, - Data = tostring(err), - }) - else - -- sender gets an immediate debit notice as the quantity is debited from their balance - ao.send({ - Target = from, - Recipient = recipient, - Quantity = quantity, - Tags = { Action = "Debit-Notice", ["Vault-Id"] = msgId }, - Data = json.encode(result), - }) - -- to the receiver, they get a vault notice - ao.send({ - Target = recipient, - Quantity = quantity, - Sender = from, - Tags = { Action = "Create-Vault-Notice", ["Vault-Id"] = msgId }, - Data = json.encode(result), + Data = tostring(error), }) + end, vaults.vaultedTransfer, from, recipient, quantity, lockLengthMs, timestamp, msgId) + if not shouldContinue2 then + return + end + + if vault ~= nil then + msg.ioEvent:addField("Vault-Id", msgId) + msg.ioEvent:addField("Vault-Balance", vault.balance) + msg.ioEvent:addField("Vault-Start-Timestamp", vault.startTimestamp) + msg.ioEvent:addField("Vault-End-Timestamp", vault.endTimestamp) end + + lastKnownLockedSupply = lastKnownLockedSupply + quantity + lastKnownCirculatingSupply = lastKnownCirculatingSupply - quantity + msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) + msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + + -- sender gets an immediate debit notice as the quantity is debited from their balance + ao.send({ + Target = from, + Recipient = recipient, + Quantity = quantity, + Tags = { Action = "Debit-Notice", ["Vault-Id"] = msgId }, + Data = json.encode(vault), + }) + -- to the receiver, they get a vault notice + ao.send({ + Target = recipient, + Quantity = quantity, + Sender = from, + Tags = { Action = "Create-Vault-Notice", ["Vault-Id"] = msgId }, + Data = json.encode(vault), + }) end) addEventingHandler(ActionMap.ExtendVault, utils.hasMatchingTag("Action", ActionMap.ExtendVault), function(msg) @@ -518,6 +568,11 @@ addEventingHandler(ActionMap.IncreaseVault, utils.hasMatchingTag("Action", Actio msg.ioEvent:addField("VaultEndTimestamp", vault.endTimestamp) end + lastKnownLockedSupply = lastKnownLockedSupply + quantity + lastKnownCirculatingSupply = lastKnownCirculatingSupply - quantity + msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) + msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + ao.send({ Target = msg.From, Tags = { Action = "Vault-Increased-Notice" }, @@ -581,6 +636,8 @@ addEventingHandler(ActionMap.BuyRecord, utils.hasMatchingTag("Action", ActionMap if result ~= nil then record = result.record addRecordResultFields(msg.ioEvent, result) + lastKnownCirculatingSupply = lastKnownCirculatingSupply - record.purchasePrice + msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) end msg.ioEvent:addField("Records-Count", utils.lengthOfTable(NameRegistry.records)) @@ -628,6 +685,8 @@ addEventingHandler(ActionMap.ExtendLease, utils.hasMatchingTag("Action", ActionM recordResult = result.record addRecordResultFields(msg.ioEvent, result) msg.ioEvent:addField("totalExtensionFee", recordResult.totalExtensionFee) + lastKnownCirculatingSupply = lastKnownCirculatingSupply - recordResult.totalExtensionFee + msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) end ao.send({ @@ -687,6 +746,8 @@ addEventingHandler( addRecordResultFields(msg.ioEvent, result) msg.ioEvent:addField("previousUndernameLimit", recordResult.undernameLimit - tonumber(msg.Tags.Quantity)) msg.ioEvent:addField("additionalUndernameCost", recordResult.additionalUndernameCost) + lastKnownCirculatingSupply = lastKnownCirculatingSupply - recordResult.additionalUndernameCost + msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) end ao.send({ @@ -809,7 +870,7 @@ addEventingHandler(ActionMap.JoinNetwork, utils.hasMatchingTag("Action", ActionM local timestamp = tonumber(msg.Timestamp) msg.ioEvent:addField("Resolved-Observer-Address", formattedObserverAddress) - msg.ioEvent:addField("Sender-Previous-Balance", fromBalance) + msg.ioEvent:addField("Sender-Previous-Balance", Balances[fromAddress] or 0) local shouldContinue, gateway = eventingPcall(msg.ioEvent, function(error) ao.send({ @@ -822,7 +883,7 @@ addEventingHandler(ActionMap.JoinNetwork, utils.hasMatchingTag("Action", ActionM return end - msg.ioEvent:addField("Sender-New-Balance", fromBalance) + msg.ioEvent:addField("Sender-New-Balance", Balances[fromAddress] or 0) if gateway ~= nil then msg.ioEvent:addField("GW-Start-Timestamp", gateway.startTimestamp) end @@ -830,6 +891,11 @@ addEventingHandler(ActionMap.JoinNetwork, utils.hasMatchingTag("Action", ActionM msg.ioEvent:addField("Joined-Gateways-Count", gwStats.joined) msg.ioEvent:addField("Leaving-Gateways-Count", gwStats.leaving) + lastKnownCirculatingSupply = lastKnownCirculatingSupply - stake + lastKnownStakedSupply = lastKnownStakedSupply + stake + msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + ao.send({ Target = fromAddress, Tags = { Action = "Join-Network-Notice" }, @@ -838,6 +904,13 @@ addEventingHandler(ActionMap.JoinNetwork, utils.hasMatchingTag("Action", ActionM end) addEventingHandler(ActionMap.LeaveNetwork, utils.hasMatchingTag("Action", ActionMap.LeaveNetwork), function(msg) + local gatewayBeforeLeaving = gar.getGateway(from) + local gwPrevTotalDelegatedStake = 0 + local gwPrevStake = 0 + if gatewayBeforeLeaving ~= nil then + gwPrevTotalDelegatedStake = gatewayBeforeLeaving.totalDelegatedStake + gwPrevStake = gatewayBeforeLeaving.operatorStake + end local shouldContinue, gateway = eventingPcall(msg.ioEvent, function(error) ao.send({ Target = msg.From, @@ -882,6 +955,11 @@ addEventingHandler(ActionMap.LeaveNetwork, utils.hasMatchingTag("Action", Action msg.ioEvent:addField("Joined-Gateways-Count", gwStats.joined) msg.ioEvent:addField("Leaving-Gateways-Count", gwStats.leaving) + lastKnownStakedSupply = lastKnownStakedSupply - gwPrevStake - gwPrevTotalDelegatedStake + lastKnownWithdrawSupply = lastKnownWithdrawSupply + gwPrevStake + gwPrevTotalDelegatedStake + msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) + ao.send({ Target = msg.From, Tags = { Action = "Leave-Network-Notice" }, @@ -911,7 +989,7 @@ addEventingHandler( return end - msg.ioEvent:addField("Sender-Previous-Balance", balances[msg.From]) + msg.ioEvent:addField("Sender-Previous-Balance", Balances[msg.From]) local quantity = tonumber(msg.Tags.Quantity) local shouldContinue2, gateway = eventingPcall(msg.ioEvent, function(error) @@ -925,12 +1003,17 @@ addEventingHandler( return end - msg.ioEvent:addField("Sender-New-Balance", balances[msg.From]) + msg.ioEvent:addField("Sender-New-Balance", Balances[msg.From]) if gateway ~= nil then msg.ioEvent:addField("New-Operator-Stake", gateway.operatorStake) msg.ioEvent:addField("Previous-Operator-Stake", gateway.operatorStake - quantity) end + lastKnownCirculatingSupply = lastKnownCirculatingSupply - quantity + lastKnownStakedSupply = lastKnownStakedSupply + quantity + msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + ao.send({ Target = msg.From, Tags = { Action = "Increase-Operator-Stake-Notice" }, @@ -962,7 +1045,7 @@ addEventingHandler( end local quantity = tonumber(msg.Tags.Quantity) - msg.ioEvent:addField("Sender-Previous-Balance", balances[msg.From]) + msg.ioEvent:addField("Sender-Previous-Balance", Balances[msg.From]) local shouldContinue2, gateway = eventingPcall(msg.ioEvent, function(error) ao.send({ @@ -975,7 +1058,7 @@ addEventingHandler( return end - msg.ioEvent:addField("Sender-New-Balance", balances[msg.From]) -- should be unchanged + msg.ioEvent:addField("Sender-New-Balance", Balances[msg.From]) -- should be unchanged if gateway ~= nil then local previousStake = gateway.operatorStake + quantity msg.ioEvent:addField("New-Operator-Stake", gateway.operatorStake) @@ -992,6 +1075,11 @@ addEventingHandler( end end + lastKnownStakedSupply = lastKnownStakedSupply - quantity + lastKnownWithdrawSupply = lastKnownWithdrawSupply + quantity + msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) + ao.send({ Target = msg.From, Tags = { Action = "Decrease-Operator-Stake-Notice" }, @@ -1045,6 +1133,11 @@ addEventingHandler(ActionMap.DelegateStake, utils.hasMatchingTag("Action", Actio delegateResult = gateway.delegates[from] end + lastKnownCirculatingSupply = lastKnownCirculatingSupply - quantity + lastKnownStakedSupply = lastKnownStakedSupply + quantity + msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + ao.send({ Target = msg.From, Tags = { Action = "Delegate-Stake-Notice", Gateway = msg.Tags.Target }, @@ -1096,9 +1189,15 @@ addEventingHandler( if result.delegate ~= nil then delegateResult = result.delegate local newStake = delegateResult.delegatedStake - msg.ioEvent:addField("PreviousStake", newStake - delegateResult.vaults[vaultId].balance) + local vaultBalance = delegateResult.vaults[vaultId].balance + msg.ioEvent:addField("PreviousStake", newStake - vaultBalance) msg.ioEvent:addField("NewStake", newStake) msg.ioEvent:addField("GatewayTotalDelegatedStake", result.totalDelegatedStake) + + lastKnownStakedSupply = lastKnownStakedSupply + vaultBalance + lastKnownWithdrawSupply = lastKnownWithdrawSupply - vaultBalance + msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) end end @@ -1236,17 +1335,22 @@ addEventingHandler( delegateResult = gateway.delegates[from] local newDelegateVaults = delegateResult.vaults if newDelegateVaults ~= nil then - msg.ioEvent:addField("VaultsCount", utils.lengthOfTable(newDelegateVaults)) + msg.ioEvent:addField("Vaults-Count", utils.lengthOfTable(newDelegateVaults)) local newDelegateVault = newDelegateVaults[msg.Id] if newDelegateVault ~= nil then msg.ioEvent:addField("Vault-Id", msg.Id) msg.ioEvent:addField("Vault-Balance", newDelegateVault.balance) - msg.ioEvent:addField("Vault-Start-Timestamp", newDelegateVault.startTimestamp) + msg.ioEvent:addField("Vaul-Start-Timestamp", newDelegateVault.startTimestamp) msg.ioEvent:addField("Vault-End-Timestamp", newDelegateVault.endTimestamp) end end end + lastKnownStakedSupply = lastKnownStakedSupply - quantity + lastKnownWithdrawSupply = lastKnownWithdrawSupply + quantity + msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) + ao.send({ Target = from, Tags = { Action = "Decrease-Delegate-Stake-Notice", Address = target, Quantity = quantity }, @@ -1423,7 +1527,8 @@ addEventingHandler("totalTokenSupply", utils.hasMatchingTag("Action", "Total-Tok for _, balance in pairs(userBalances) do circulatingSupply = circulatingSupply + balance end - totalSupply = totalSupply + circulatingSupply + circulatingSupply = circulatingSupply - protocolBalance + totalSupply = protocolBalance + circulatingSupply -- tally supply stashed in gateways and delegates local gateways = gar.getGateways() diff --git a/src/tick.lua b/src/tick.lua index 11c9358..3eded66 100644 --- a/src/tick.lua +++ b/src/tick.lua @@ -6,13 +6,13 @@ local epochs = require("epochs") function tick.pruneState(timestamp, msgId) local prunedRecords = arns.pruneRecords(timestamp) arns.pruneReservedNames(timestamp) - vaults.pruneVaults(timestamp) + local prunedVaults = vaults.pruneVaults(timestamp) local gatewayResults = gar.pruneGateways(timestamp, msgId) local prunedEpochs = epochs.pruneEpochs(timestamp) return { prunedRecords = prunedRecords, - prunedGateways = gatewayResults.prunedGateways, - slashedGateways = gatewayResults.slashedGateways, + prunedVaults = prunedVaults, + pruneGatewayResults = gatewayResults, prunedEpochs = prunedEpochs, } end diff --git a/src/vaults.lua b/src/vaults.lua index f2f6f4d..75de114 100644 --- a/src/vaults.lua +++ b/src/vaults.lua @@ -139,18 +139,22 @@ end -- return any vaults to owners that have expired function vaults.pruneVaults(currentTimestamp) local allVaults = vaults.getVaults() - for owner, vaults in pairs(allVaults) do - for id, nestedVault in pairs(vaults) do + local prunedVaults = {} + for owner, ownersVaults in pairs(allVaults) do + for id, nestedVault in pairs(ownersVaults) do if currentTimestamp >= nestedVault.endTimestamp then balances.increaseBalance(owner, nestedVault.balance) - vaults[id] = nil + ownersVaults[id] = nil + prunedVaults[id] = nestedVault end end -- update the owner vault - allVaults[owner] = vaults + -- TODO: I THINK THIS LINE IS UNNECESSARY. CHECK TESTS + allVaults[owner] = ownersVaults end -- set the vaults to the updated vaults Vaults = allVaults + return prunedVaults end return vaults diff --git a/tests/arns.test.mjs b/tests/arns.test.mjs index b012d8b..79e3d60 100644 --- a/tests/arns.test.mjs +++ b/tests/arns.test.mjs @@ -109,6 +109,12 @@ describe('ArNS', async () => { 'Protocol-Balance': expectedRemainingBalance[PROCESS_ID], 'Reserved-Records-Count': 0, 'Remaining-Balance': expectedRemainingBalance[sender], + 'Circulating-Supply': -600000000, // Artifact of starting out without initializing this properly + 'Total-Supply': 0, // Artifact of starting out without initializing this properly + 'Staked-Supply': 0, + 'Delegated-Supply': 0, + 'Locked-Supply': 0, + 'Withdraw-Supply': 0, }); // fetch the record diff --git a/tests/handlers.test.mjs b/tests/handlers.test.mjs index 763f6f5..ffa33b7 100644 --- a/tests/handlers.test.mjs +++ b/tests/handlers.test.mjs @@ -76,8 +76,8 @@ describe('handlers', async () => { 'total supply should be 1 billion IO but was ' + supplyData.total, ); assert.ok( - supplyData.circulating === 1000000000 * 1000000, - 'circulating supply should be 1 billion IO but was ' + + supplyData.circulating === 1000000000 * 1000000 - 50000000000000, + 'circulating supply should be 0.95 billion IO but was ' + supplyData.circulating, ); assert.ok( From 86a2c2f7664817079bd598ec6635537b227f6fbe Mon Sep 17 00:00:00 2001 From: Ariel Melendez Date: Fri, 18 Oct 2024 13:10:40 -0700 Subject: [PATCH 25/41] feat(events): excise unnecessary code PE-6796 --- src/vaults.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vaults.lua b/src/vaults.lua index 75de114..2395f87 100644 --- a/src/vaults.lua +++ b/src/vaults.lua @@ -148,9 +148,6 @@ function vaults.pruneVaults(currentTimestamp) prunedVaults[id] = nestedVault end end - -- update the owner vault - -- TODO: I THINK THIS LINE IS UNNECESSARY. CHECK TESTS - allVaults[owner] = ownersVaults end -- set the vaults to the updated vaults Vaults = allVaults From e560ab238eb8ca7f02b6a79cd904827f148778e1 Mon Sep 17 00:00:00 2001 From: Ariel Melendez Date: Fri, 18 Oct 2024 14:31:50 -0700 Subject: [PATCH 26/41] feat(events): only send supply event data on change PE-6796 --- src/main.lua | 53 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/src/main.lua b/src/main.lua index 3591c6e..364001a 100644 --- a/src/main.lua +++ b/src/main.lua @@ -86,12 +86,18 @@ local ActionMap = { } -- Low fidelity trackers -local lastKnownTotalSupply = 0 local lastKnownCirculatingSupply = 0 local lastKnownLockedSupply = 0 local lastKnownStakedSupply = 0 local lastKnownDelegatedSupply = 0 local lastKnownWithdrawSupply = 0 +local function lastKnownTotalTokenSupply() + return lastKnownCirculatingSupply + + lastKnownLockedSupply + + lastKnownStakedSupply + + lastKnownDelegatedSupply + + Balances[Protocol] +end local function eventingPcall(ioEvent, onError, fnToCall, ...) local status, result = pcall(fnToCall, ...) @@ -168,6 +174,12 @@ end, function(msg) NameRegistry = utils.deepCopy(NameRegistry), Epochs = utils.deepCopy(Epochs), Balances = utils.deepCopy(Balances), + lastKnownCirculatingSupply = lastKnownCirculatingSupply, + lastKnownLockedSupply = lastKnownLockedSupply, + lastKnownStakedSupply = lastKnownStakedSupply, + lastKnownDelegatedSupply = lastKnownDelegatedSupply, + lastKnownWithdrawSupply = lastKnownWithdrawSupply, + lastKnownTotalSupply = lastKnownTotalTokenSupply(), } local status, resultOrError = pcall(tick.pruneState, msgTimestamp, msgId) if not status then @@ -236,20 +248,27 @@ end, function(msg) end end - msg.ioEvent:addField( - "Total-Supply", - lastKnownCirculatingSupply - + lastKnownLockedSupply - + lastKnownStakedSupply - + lastKnownDelegatedSupply - + lastKnownWithdrawSupply - ) - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) - msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) - msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) - msg.ioEvent:addField("Delegated-Supply", lastKnownDelegatedSupply) - msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) - msg.ioEvent:addField("Protocol-Balance", Balances[Protocol]) + if lastKnownCirculatingSupply ~= previousState.lastKnownCirculatingSupply then + msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + end + if lastKnownLockedSupply ~= previousState.lastKnownLockedSupply then + msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) + end + if lastKnownStakedSupply ~= previousState.lastKnownStakedSupply then + msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + end + if lastKnownDelegatedSupply ~= previousState.lastKnownDelegatedSupply then + msg.ioEvent:addField("Delegated-Supply", lastKnownDelegatedSupply) + end + if lastKnownWithdrawSupply ~= previousState.lastKnownWithdrawSupply then + msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) + end + if Balances[Protocol] ~= previousState.Balances[Protocol] then + msg.ioEvent:addField("Protocol-Balance", Balances[Protocol]) + end + if lastKnownTotalTokenSupply() ~= previousState.lastKnownTotalSupply then + msg.ioEvent:addField("Total-Token-Supply", lastKnownTotalTokenSupply()) + end return status end) @@ -1560,14 +1579,14 @@ addEventingHandler("totalTokenSupply", utils.hasMatchingTag("Action", "Total-Tok end end - lastKnownTotalSupply = totalSupply lastKnownCirculatingSupply = circulatingSupply lastKnownLockedSupply = lockedSupply lastKnownStakedSupply = stakedSupply lastKnownDelegatedSupply = delegatedSupply lastKnownWithdrawSupply = withdrawSupply - msg.ioEvent:addField("Total-Token-Supply", lastKnownTotalSupply) + msg.ioEvent:addField("Total-Token-Supply", totalSupply) + msg.ioEvent:addField("Last-Known-Total-Token-Supply", lastKnownTotalTokenSupply()) msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) From 28512cc757342bfafe4e73ee6ae5d100a1f3f1b2 Mon Sep 17 00:00:00 2001 From: Ariel Melendez Date: Fri, 18 Oct 2024 14:56:18 -0700 Subject: [PATCH 27/41] feat(events): identify invariant slashed gws PE-6796 --- src/main.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main.lua b/src/main.lua index 364001a..cac0d83 100644 --- a/src/main.lua +++ b/src/main.lua @@ -245,6 +245,16 @@ end, function(msg) if slashedGatewaysCount > 0 then msg.ioEvent:addField("Slashed-Gateways", slashedGateways) msg.ioEvent:addField("Slashed-Gateways-Count", slashedGatewaysCount) + local invariantSlashedGateways = {} + for _, gwAddress in pairs(slashedGateways) do + local gw = gar.getGateway(gwAddress) or {} + if gw.totalDelegatedStake > 0 then + invariantSlashedGateways[gwAddress] = gw.totalDelegatedStake + end + end + if utils.lengthOfTable(invariantSlashedGateways) > 0 then + msg.ioEvent:addField("Invariant-Slashed-Gateways", invariantSlashedGateways) + end end end From f8e41f50581a255e0d3d2cddbdaa9e946841df31 Mon Sep 17 00:00:00 2001 From: Ariel Melendez Date: Fri, 18 Oct 2024 15:00:35 -0700 Subject: [PATCH 28/41] test(events): test fix PE-6796 --- tests/arns.test.mjs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/arns.test.mjs b/tests/arns.test.mjs index 79e3d60..b3f62df 100644 --- a/tests/arns.test.mjs +++ b/tests/arns.test.mjs @@ -110,11 +110,6 @@ describe('ArNS', async () => { 'Reserved-Records-Count': 0, 'Remaining-Balance': expectedRemainingBalance[sender], 'Circulating-Supply': -600000000, // Artifact of starting out without initializing this properly - 'Total-Supply': 0, // Artifact of starting out without initializing this properly - 'Staked-Supply': 0, - 'Delegated-Supply': 0, - 'Locked-Supply': 0, - 'Withdraw-Supply': 0, }); // fetch the record From ed7dfdac1b99e5d824cdeb18b8012c249116db98 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 19 Oct 2024 07:41:30 -0500 Subject: [PATCH 29/41] chore(monitor): modify monitor to check for testnet --- tests/monitor/monitor.test.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/monitor/monitor.test.mjs b/tests/monitor/monitor.test.mjs index 06fcb66..4135afc 100644 --- a/tests/monitor/monitor.test.mjs +++ b/tests/monitor/monitor.test.mjs @@ -4,9 +4,10 @@ import { strict as assert } from 'node:assert'; import { describe, it, before, after } from 'node:test'; import { DockerComposeEnvironment, Wait } from 'testcontainers'; +const processId = process.env.IO_PROCESS_ID || IO_TESTNET_PROCESS_ID; const io = IO.init({ process: new AOProcess({ - processId: process.env.IO_PROCESS_ID || IO_TESTNET_PROCESS_ID, + processId, ao: connect({ CU_URL: 'http://localhost:6363', }), @@ -33,7 +34,8 @@ describe('setup', () => { describe('handlers', () => { it('should always have correct number of handlers', async () => { - const expectedHandlerCount = 53; // TODO: update this if more handlers are added + const expectedHandlerCount = + processId === IO_TESTNET_PROCESS_ID ? 52 : 53; // TODO: update this if more handlers are added const { Handlers: handlersList } = await io.getInfo(); /** * There are two security handlers before _eval and _default, so count is 52 From 25af6199893351d1e522b2eaf43302e6a5844c4a Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 19 Oct 2024 08:04:10 -0500 Subject: [PATCH 30/41] chore(utils): add pagination tests --- spec/balances_spec.lua | 19 +++++++++++++++++++ spec/gar_spec.lua | 27 +++++++++++++++++++++++++++ src/utils.lua | 13 +++++++------ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/spec/balances_spec.lua b/spec/balances_spec.lua index 38c5eea..788cade 100644 --- a/spec/balances_spec.lua +++ b/spec/balances_spec.lua @@ -44,4 +44,23 @@ describe("balances", function() assert.are.equal(0, balances.getBalance(testAddress2)) assert.are.equal(100, balances.getBalance(testAddress1)) end) + + describe("getPaginatedBalances", function() + it("should return paginated balances", function() + local balances = balances.getPaginatedBalances(nil, 10, "balance", "desc") + assert.are.same(balances, { + limit = 10, + sortBy = "balance", + sortOrder = "desc", + hasMore = false, + totalItems = 1, + items = { + { + address = testAddress1, + balance = 100, + }, + }, + }) + end) + end) end) diff --git a/spec/gar_spec.lua b/spec/gar_spec.lua index 0dd0738..e98cb11 100644 --- a/spec/gar_spec.lua +++ b/spec/gar_spec.lua @@ -1798,4 +1798,31 @@ describe("gar", function() }) end) end) + + describe("getPaginatedGateways", function() + it("should return paginated gateways sorted by startTimestamp in ascending order (oldest first)", function() + local gateway1 = utils.deepCopy(testGateway) + local gateway2 = utils.deepCopy(testGateway) + gateway1.startTimestamp = 1000 + gateway2.startTimestamp = 0 + _G.GatewayRegistry = { + [stubGatewayAddress] = gateway1, + [stubRandomAddress] = gateway2, + } + local gateways = gar.getPaginatedGateways(nil, 10, "startTimestamp", "asc") + gateway1.gatewayAddress = stubGatewayAddress + gateway2.gatewayAddress = stubRandomAddress + assert.are.same({ + limit = 10, + sortBy = "startTimestamp", + sortOrder = "asc", + hasMore = false, + totalItems = 2, + items = { + gateway2, -- should be first because it has a lower startTimestamp + gateway1, + }, + }, gateways) + end) + end) end) diff --git a/src/utils.lua b/src/utils.lua index 99230d7..c853f1d 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -89,15 +89,16 @@ end function utils.paginateTableWithCursor(tableArray, cursor, cursorField, limit, sortBy, sortOrder) local sortedTable = utils.sortTableByField(tableArray, sortBy, sortOrder) - if not sortedTable or #sortedTable == 0 then + + if not sortedTable or utils.lengthOfTable(sortedTable) == 0 then return { items = {}, limit = limit, totalItems = 0, - totalPages = 0, sortBy = sortBy, sortOrder = sortOrder, nextCursor = nil, + hasMore = false, } end @@ -113,25 +114,25 @@ function utils.paginateTableWithCursor(tableArray, cursor, cursorField, limit, s end local items = {} - local endIndex = math.min(startIndex + limit - 1, #sortedTable) + local endIndex = math.min(startIndex + limit - 1, utils.lengthOfTable(sortedTable)) for i = startIndex, endIndex do table.insert(items, sortedTable[i]) end local nextCursor = nil - if endIndex < #sortedTable then + if endIndex < utils.lengthOfTable(sortedTable) then nextCursor = sortedTable[endIndex][cursorField] end return { items = items, limit = limit, - totalItems = #sortedTable, + totalItems = utils.lengthOfTable(sortedTable), sortBy = sortBy, sortOrder = sortOrder, nextCursor = nextCursor, - hasMore = nextCursor ~= nil, + hasMore = nextCursor ~= nil and true or false, } end From bf247d82f75434d1505b0419c20181019d695292 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 19 Oct 2024 08:06:07 -0500 Subject: [PATCH 31/41] chore: remove redundant and in hasMore value --- src/utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.lua b/src/utils.lua index c853f1d..ce6b2d6 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -132,7 +132,7 @@ function utils.paginateTableWithCursor(tableArray, cursor, cursorField, limit, s sortBy = sortBy, sortOrder = sortOrder, nextCursor = nextCursor, - hasMore = nextCursor ~= nil and true or false, + hasMore = nextCursor ~= nil, } end From fa433b6ab3d5e578e38f299bb386db976a52d429 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 19 Oct 2024 08:07:45 -0500 Subject: [PATCH 32/41] chore(utils): rename from sortedTable to sortedArray --- src/utils.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/utils.lua b/src/utils.lua index ce6b2d6..61e6312 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -88,9 +88,9 @@ function utils.sortTableByField(prevTable, field, order) end function utils.paginateTableWithCursor(tableArray, cursor, cursorField, limit, sortBy, sortOrder) - local sortedTable = utils.sortTableByField(tableArray, sortBy, sortOrder) + local sortedArray = utils.sortTableByField(tableArray, sortBy, sortOrder) - if not sortedTable or utils.lengthOfTable(sortedTable) == 0 then + if not sortedArray or #sortedArray == 0 then return { items = {}, limit = limit, @@ -105,7 +105,7 @@ function utils.paginateTableWithCursor(tableArray, cursor, cursorField, limit, s local startIndex = 1 if cursor then - for i, obj in ipairs(sortedTable) do + for i, obj in ipairs(sortedArray) do if obj[cursorField] == cursor then startIndex = i + 1 break @@ -114,21 +114,21 @@ function utils.paginateTableWithCursor(tableArray, cursor, cursorField, limit, s end local items = {} - local endIndex = math.min(startIndex + limit - 1, utils.lengthOfTable(sortedTable)) + local endIndex = math.min(startIndex + limit - 1, #sortedArray) for i = startIndex, endIndex do - table.insert(items, sortedTable[i]) + table.insert(items, sortedArray[i]) end local nextCursor = nil - if endIndex < utils.lengthOfTable(sortedTable) then - nextCursor = sortedTable[endIndex][cursorField] + if endIndex < #sortedArray then + nextCursor = sortedArray[endIndex][cursorField] end return { items = items, limit = limit, - totalItems = utils.lengthOfTable(sortedTable), + totalItems = #sortedArray, sortBy = sortBy, sortOrder = sortOrder, nextCursor = nextCursor, From 44dac4fa32607670423bf7a7a257dd32c38234b0 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 19 Oct 2024 08:19:53 -0500 Subject: [PATCH 33/41] chore(test): add fetching next page --- spec/gar_spec.lua | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/spec/gar_spec.lua b/spec/gar_spec.lua index e98cb11..dfb388f 100644 --- a/spec/gar_spec.lua +++ b/spec/gar_spec.lua @@ -1769,7 +1769,7 @@ describe("gar", function() startTimestamp = timestamp + 10, -- joined after the timestamp status = "joined", }, - ["test-this-is-valid-arweave-wallet-address-3"] = { + ["test-this-is-valid-arweave-wallet-address-4"] = { startTimestamp = timestamp - 10, -- joined before the timestamp, but leaving endTimestamp = timestamp + 100, status = "leaving", @@ -1809,20 +1809,33 @@ describe("gar", function() [stubGatewayAddress] = gateway1, [stubRandomAddress] = gateway2, } - local gateways = gar.getPaginatedGateways(nil, 10, "startTimestamp", "asc") + local gateways = gar.getPaginatedGateways(nil, 1, "startTimestamp", "asc") gateway1.gatewayAddress = stubGatewayAddress gateway2.gatewayAddress = stubRandomAddress assert.are.same({ - limit = 10, + limit = 1, sortBy = "startTimestamp", sortOrder = "asc", - hasMore = false, + hasMore = true, + nextCursor = stubRandomAddress, totalItems = 2, items = { gateway2, -- should be first because it has a lower startTimestamp - gateway1, }, }, gateways) + -- get the next page + local nextGateways = gar.getPaginatedGateways(gateways.nextCursor, 1, "startTimestamp", "asc") + assert.are.same({ + limit = 1, + sortBy = "startTimestamp", + sortOrder = "asc", + hasMore = false, + nextCursor = nil, + totalItems = 2, + items = { + gateway1, + }, + }, nextGateways) end) end) end) From 17ff1033bad7900cdd31ec919fff03d415d412c8 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 19 Oct 2024 08:59:26 -0500 Subject: [PATCH 34/41] chore(test): add more test and handle nils in paginated sort --- spec/arns_spec.lua | 124 +++++++++++++++++++++++++++++++++++++++++++++ src/utils.lua | 18 ++++--- 2 files changed, 136 insertions(+), 6 deletions(-) diff --git a/spec/arns_spec.lua b/spec/arns_spec.lua index b798daf..e82d242 100644 --- a/spec/arns_spec.lua +++ b/spec/arns_spec.lua @@ -584,4 +584,128 @@ describe("arns", function() assert.are.equal(registrationFees["51"].lease["1"], 480000000) end) end) + + describe("getPaginatedRecords", function() + before_each(function() + _G.NameRegistry = { + records = { + ["active-record"] = { + endTimestamp = 100, -- far in the future + processId = "oldest-process-id", + purchasePrice = 600000000, + startTimestamp = 0, + type = "lease", + undernameLimit = 10, + }, + ["active-record-1"] = { + endTimestamp = 10000, + processId = "middle-process-id", + purchasePrice = 400000000, + startTimestamp = 0, + type = "lease", + undernameLimit = 5, + }, + ["active-record-2"] = { + endTimestamp = 10000000, + processId = "newest-process-id", + purchasePrice = 500000000, + startTimestamp = 0, + type = "lease", + undernameLimit = 8, + }, + ["permabuy-record"] = { + endTimestamp = nil, + processId = "permabuy-process-id", + purchasePrice = 600000000, + startTimestamp = 0, + type = "permabuy", + undernameLimit = 10, + }, + }, + } + end) + + it("should return the correct paginated records with ascending putting permabuy at the end", function() + local paginatedRecords = arns.getPaginatedRecords(nil, 1, "endTimestamp", "asc") + assert.are.same({ + limit = 1, + sortBy = "endTimestamp", + sortOrder = "asc", + hasMore = true, + totalItems = 4, + nextCursor = "active-record", + items = { + { + name = "active-record", + endTimestamp = 100, + processId = "oldest-process-id", + purchasePrice = 600000000, + startTimestamp = 0, + type = "lease", + undernameLimit = 10, + }, + }, + }, paginatedRecords) + local paginatedRecords2 = arns.getPaginatedRecords(paginatedRecords.nextCursor, 1, "endTimestamp", "asc") + assert.are.same({ + limit = 1, + sortBy = "endTimestamp", + sortOrder = "asc", + hasMore = true, + totalItems = 4, + nextCursor = "active-record-1", + items = { + { + name = "active-record-1", + endTimestamp = 10000, + processId = "middle-process-id", + purchasePrice = 400000000, + startTimestamp = 0, + type = "lease", + undernameLimit = 5, + }, + }, + }, paginatedRecords2) + local paginatedRecords3 = arns.getPaginatedRecords(paginatedRecords2.nextCursor, 1, "endTimestamp", "asc") + assert.are.same({ + limit = 1, + sortBy = "endTimestamp", + sortOrder = "asc", + hasMore = true, + totalItems = 4, + nextCursor = "active-record-2", + items = { + { + name = "active-record-2", + endTimestamp = 10000000, + processId = "newest-process-id", + purchasePrice = 500000000, + startTimestamp = 0, + type = "lease", + undernameLimit = 8, + }, + }, + }, paginatedRecords3) + local paginatedRecords4 = arns.getPaginatedRecords(paginatedRecords3.nextCursor, 1, "endTimestamp", "asc") + assert.are.same({ + limit = 1, + sortBy = "endTimestamp", + sortOrder = "asc", + hasMore = false, + totalItems = 4, + nextCursor = nil, + items = { + { + name = "permabuy-record", + endTimestamp = nil, + processId = "permabuy-process-id", + purchasePrice = 600000000, + startTimestamp = 0, + type = "permabuy", + undernameLimit = 10, + }, + }, + }, paginatedRecords4) + end) + end) end) diff --git a/src/utils.lua b/src/utils.lua index 61e6312..bdb657f 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -72,16 +72,22 @@ function utils.sortTableByField(prevTable, field, order) end table.sort(tableCopy, function(a, b) - -- If the field is not present in the table, return false - if not a[field] or not b[field] then - print("Field not found in table, skipping:" .. field .. " " .. json.encode(a) .. " " .. json.encode(b)) + local aField = a[field] + local bField = b[field] + -- If one field is nil, ensure it goes to the end + if aField == nil and bField ~= nil then + return false + elseif aField ~= nil and bField == nil then + return true + elseif aField == nil and bField == nil then + -- If both fields are nil, consider them equal return false end if order == "asc" then - return a[field] < b[field] + return aField < bField else - return a[field] > b[field] + return aField > bField end end) return tableCopy @@ -131,7 +137,7 @@ function utils.paginateTableWithCursor(tableArray, cursor, cursorField, limit, s totalItems = #sortedArray, sortBy = sortBy, sortOrder = sortOrder, - nextCursor = nextCursor, + nextCursor = nextCursor, -- the last item in the current page hasMore = nextCursor ~= nil, } end From b36715de0c73ccd2b62f812b2372e7358b7c6001 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 19 Oct 2024 09:05:52 -0500 Subject: [PATCH 35/41] chore(utils): remove unused utils --- src/utils.lua | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) diff --git a/src/utils.lua b/src/utils.lua index bdb657f..c637192 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -7,10 +7,6 @@ function utils.hasMatchingTag(tag, value) return Handlers.utils.hasMatchingTag(tag, value) end -function utils.reply(msg) - Handlers.utils.reply(msg) -end - -- Then, check for a 43-character base64url pattern. -- The pattern checks for a string of length 43 containing alphanumeric characters, hyphens, or underscores. function utils.isValidBase64Url(url) @@ -197,42 +193,6 @@ function utils.safeDecodeJson(jsonString) return result end -function utils.validateFQDN(fqdn) - -- Check if the fqdn is not nil and not empty - if not fqdn or fqdn == "" then - error("FQDN is empty") - end - - -- Split the fqdn into parts by dot and validate each part - local parts = {} - for part in fqdn:gmatch("[^%.]+") do - table.insert(parts, part) - end - - -- Validate each part of the domain - for _, part in ipairs(parts) do - -- Check that the part length is between 1 and 63 characters - if #part < 1 or #part > 63 then - error("Invalid fqdn format: each part must be between 1 and 63 characters") - end - -- Check that the part does not start or end with a hyphen - if part:match("^-") or part:match("-$") then - error("Invalid fqdn format: parts must not start or end with a hyphen") - end - -- Check that the part contains only alphanumeric characters and hyphen - if not part:match("^[A-Za-z0-9-]+$") then - error("Invalid fqdn format: parts must contain only alphanumeric characters or hyphen") - end - end - - -- Check if there is at least one top-level domain (TLD) - if #parts < 2 then - error("Invalid fqdn format: missing top-level domain") - end - - return fqdn -end - function utils.findInArray(array, predicate) for i = 1, #array do if predicate(array[i]) then @@ -246,14 +206,6 @@ function utils.walletHasSufficientBalance(wallet, quantity) return Balances[wallet] ~= nil and Balances[wallet] >= quantity end -function utils.copyTable(table) - local copy = {} - for key, value in pairs(table) do - copy[key] = value - end - return copy -end - function utils.deepCopy(original) if not original then return nil From 9f197a962d9769d963766b5b0feda4b2f308ffa1 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 19 Oct 2024 09:11:48 -0500 Subject: [PATCH 36/41] chore(test): more coverage on tests and more efficient use of copy --- spec/arns_spec.lua | 21 +++++++++++++++++++++ src/arns.lua | 19 ++----------------- src/balances.lua | 5 ++--- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/spec/arns_spec.lua b/spec/arns_spec.lua index e82d242..22574ac 100644 --- a/spec/arns_spec.lua +++ b/spec/arns_spec.lua @@ -477,6 +477,27 @@ describe("arns", function() _G.DemandFactor.currentDemandFactor = demandFactor assert.are.equal(expectedCost, arns.getTokenCost(intendedAction)) end) + it("should return the token cost for extending a name", function() + _G.NameRegistry.records["test-name"] = { + endTimestamp = timestamp + constants.oneYearMs, + processId = testProcessId, + purchasePrice = 600000000, + startTimestamp = 0, + type = "lease", + undernameLimit = 10, + } + local baseFee = 500000000 + local years = 2 + local demandFactor = 1.2405 + local expectedCost = math.floor((years * baseFee * 0.20) * demandFactor) + local intendedAction = { + intent = "Extend-Lease", + years = 2, + name = "test-name", + } + _G.DemandFactor.currentDemandFactor = demandFactor + assert.are.equal(expectedCost, arns.getTokenCost(intendedAction)) + end) end) describe("pruneRecords", function() diff --git a/src/arns.lua b/src/arns.lua index 76c68ae..bc868e0 100644 --- a/src/arns.lua +++ b/src/arns.lua @@ -165,8 +165,7 @@ function arns.increaseundernameLimit(from, name, qty, currentTimestamp) end function arns.getRecord(name) - local records = arns.getRecords() - return records[name] + return utils.deepCopy(NameRegistry.records[name]) end function arns.getActiveArNSNamesBetweenTimestamps(startTimestamp, endTimestamp) @@ -200,8 +199,7 @@ function arns.getReservedNames() end function arns.getReservedName(name) - local reserved = arns.getReservedNames() - return reserved[name] + return utils.deepCopy(NameRegistry.reserved[name]) end function arns.modifyRecordundernameLimit(name, qty) @@ -228,19 +226,6 @@ function arns.modifyRecordEndTimestamp(name, newEndTimestamp) NameRegistry.records[name].endTimestamp = newEndTimestamp end -function arns.addReservedName(name, details) - if arns.getReservedName(name) then - error("Name is already reserved") - end - - if arns.getRecord(name) then - error("Name is already registered") - end - - NameRegistry.reserved[name] = details - return arns.getReservedName(name) -end - -- internal functions function arns.calculateLeaseFee(baseFee, years, demandFactor) local annualRegistrationFee = arns.calculateAnnualRenewalFee(baseFee, years) diff --git a/src/balances.lua b/src/balances.lua index a3d6be0..587bb52 100644 --- a/src/balances.lua +++ b/src/balances.lua @@ -23,8 +23,7 @@ function balances.transfer(recipient, from, qty) end function balances.getBalance(target) - local balance = balances.getBalances()[target] - return balance or 0 + return utils.deepCopy(Balances[target] or 0) end function balances.getBalances() @@ -33,7 +32,7 @@ function balances.getBalances() end function balances.reduceBalance(target, qty) - local prevBalance = balances.getBalance(target) or 0 + local prevBalance = balances.getBalance(target) if prevBalance < qty then error("Insufficient balance") end From 616312e3f349bfa1924f3fb4e61e19e6201893ce Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 19 Oct 2024 11:18:50 -0500 Subject: [PATCH 37/41] chore(tests): add more monitor tests --- src/utils.lua | 6 +- tests/monitor/monitor.test.mjs | 78 ++++++++++++++++++++++++- yarn.lock | 100 +++++++++++---------------------- 3 files changed, 114 insertions(+), 70 deletions(-) diff --git a/src/utils.lua b/src/utils.lua index c637192..ec66ad2 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -228,8 +228,10 @@ end function utils.lengthOfTable(table) local count = 0 - for _ in pairs(table) do - count = count + 1 + for _, val in pairs(table) do + if val then + count = count + 1 + end end return count end diff --git a/tests/monitor/monitor.test.mjs b/tests/monitor/monitor.test.mjs index 4135afc..b84319e 100644 --- a/tests/monitor/monitor.test.mjs +++ b/tests/monitor/monitor.test.mjs @@ -188,6 +188,64 @@ describe('setup', () => { `Epoch index is not up to date: ${epochIndex}`, ); }); + it('should contain the startTimestamp, endTimestamp and distributions and observations for the current epoch', async () => { + const { epochIndex, startTimestamp, endTimestamp, distributions, observations } = + await io.getCurrentEpoch(); + assert(epochIndex > 0, 'Epoch index is not valid'); + assert(distributions, 'Distributions are not valid'); + assert(observations, 'Observations are not valid'); + assert( + startTimestamp > 0, + `Start timestamp is not valid: ${startTimestamp}`, + ); + assert( + endTimestamp > startTimestamp, + `End timestamp is not greater than start timestamp: ${endTimestamp} > ${startTimestamp}`, + ); + assert( + distributions.rewards.eligible, + 'Eligible rewards are not valid', + ); + + // compare the current gateway count to the current epoch totalEligibleRewards + const { items: gateways } = await io.getGateways({ + limit: 1000, // we will need to update this if the number of gateways grows + }); + const activeGatewayCount = gateways.filter( + (gateway) => gateway.status === 'joined', + ).length; + assert( + activeGatewayCount === distributions.totalEligibleGateways, + `Active gateway count (${activeGatewayCount}) does not match total eligible gateways (${distributions.totalEligibleGateways}) for the current epoch`, + ); + }); + + it('the previous epoch should have a been distributed', async () => { + const { epochIndex: currentEpochIndex } = await io.getCurrentEpoch(); + const { epochIndex, distributions, endTimestamp, startTimestamp } = + await io.getEpoch({ epochIndex: currentEpochIndex - 1 }); + assert( + epochIndex === currentEpochIndex - 1, + 'Previous epoch index is not valid', + ); + assert(distributions, 'Distributions are not valid'); + assert( + endTimestamp > startTimestamp, + 'End timestamp is not greater than start timestamp', + ); + assert( + distributions.distributedTimestamp >= endTimestamp, + 'Distributed timestamp is not greater than epoch end timestamp', + ); + assert( + distributions.rewards.eligible !== undefined, + 'Eligible rewards are not valid', + ); + assert( + distributions.rewards.distributed !== undefined, + 'Distributed rewards are not valid', + ); + }); }); // TODO: add demand factor tests @@ -203,10 +261,14 @@ describe('setup', () => { ); let cursor = ''; + let countedTotalGateways = 0; + let totalGateways = 0; do { - const { items: gateways, nextCursor } = await io.getGateways({ + const { items: gateways, nextCursor, totalItems } = await io.getGateways({ cursor, }); + totalGateways = totalItems; + countedTotalGateways += gateways.length; for (const gateway of gateways) { if (gateway.status === 'joined') { assert(gateway.operatorStake >= 50_000_000_000); @@ -276,6 +338,10 @@ describe('setup', () => { } cursor = nextCursor; } while (cursor !== undefined); + assert( + countedTotalGateways === totalGateways, + `Counted total gateways (${countedTotalGateways}) does not match total gateways (${totalGateways})`, + ); }); }); @@ -284,10 +350,14 @@ describe('setup', () => { const twoWeeks = 2 * 7 * 24 * 60 * 60 * 1000; it('should not have any arns records older than two weeks', async () => { let cursor = ''; + let countedTotalArns = 0; + let totalArns = 0; do { - const { items: arns, nextCursor } = await io.getArNSRecords({ + const { items: arns, nextCursor, totalItems } = await io.getArNSRecords({ cursor, }); + totalArns = totalItems; + countedTotalArns += arns.length; for (const arn of arns) { assert(arn.processId, `ARNs name '${arn.name}' has no processId`); assert(arn.type, `ARNs name '${arn.name}' has no type`); @@ -323,6 +393,10 @@ describe('setup', () => { } cursor = nextCursor; } while (cursor !== undefined); + assert( + countedTotalArns === totalArns, + `Counted total ARNs (${countedTotalArns}) does not match total ARNs (${totalArns})`, + ); }); }); }); diff --git a/yarn.lock b/yarn.lock index 34773e7..0c52a08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,14 +3,14 @@ "@ar.io/sdk@alpha": - version "2.2.0-alpha.1" - resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.2.0-alpha.1.tgz#3b4764bf39b1aecfb459ebf9815c2c34b4a8c32b" - integrity sha512-32zHXcruaEPcAxJlKRgBuDp0odhLake44t44qlzjg36CgeunHr/Amtehu0U6FuulGjLrh7OMkUvbyLrcurOU/w== + version "2.3.3-alpha.1" + resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.3.3-alpha.1.tgz#783ba3b24b95e8e56ed732ec76fc0a75c566b5db" + integrity sha512-vAisQKV4ap+2ARoGkOyWqarK4xCVMlTE4OWnpLJD89F2KGuLPnoA5mZmWVbk9c4S4Ht81mWCQTUDaCZ2g7qAiA== dependencies: + "@dha-team/arbundles" "^1.0.1" "@permaweb/aoconnect" "^0.0.57" - arbundles "0.11.0" arweave "1.14.4" - axios "1.7.3" + axios "1.7.7" axios-retry "^4.3.0" eventemitter3 "^5.0.1" plimit-lit "^3.0.1" @@ -36,6 +36,30 @@ enabled "2.0.x" kuler "^2.0.0" +"@dha-team/arbundles@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@dha-team/arbundles/-/arbundles-1.0.1.tgz#5e81039b74da241cf0e2b074ae77e489eec7887d" + integrity sha512-cgVxhZJLK1HG2+vcRBZ0CYGpxz7mA2QvLaspcw2gOzb2V/ZUxlifUu1aufBK3iz63Ww2OhgO0j9DstRJqrG1uA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/providers" "^5.7.2" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wallet" "^5.7.0" + "@noble/ed25519" "^1.6.1" + arweave "^1.15.1" + base64url "^3.0.1" + bs58 "^4.0.1" + keccak "^3.0.2" + secp256k1 "^5.0.0" + optionalDependencies: + "@randlabs/myalgo-connect" "^1.1.2" + algosdk "^1.13.1" + arweave-stream-tx "^1.1.0" + multistream "^4.1.0" + tmp-promise "^3.0.2" + "@ethersproject/abstract-provider@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" @@ -331,17 +355,6 @@ resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== -"@irys/arweave@^0.0.2": - version "0.0.2" - resolved "https://registry.yarnpkg.com/@irys/arweave/-/arweave-0.0.2.tgz#c0e73eb8c15e323342d33ea92701d4036fd22ae3" - integrity sha512-ddE5h4qXbl0xfGlxrtBIwzflaxZUDlDs43TuT0u1OMfyobHul4AA1VEX72Rpzw2bOh4vzoytSqA1jCM7x9YtHg== - dependencies: - asn1.js "^5.4.1" - async-retry "^1.3.3" - axios "^1.4.0" - base64-js "^1.5.1" - bignumber.js "^9.1.1" - "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -531,30 +544,6 @@ ansi-styles@^6.1.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -arbundles@0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/arbundles/-/arbundles-0.11.0.tgz#2a8bbc142aae6c56588dffd8cd4b20b931a4e578" - integrity sha512-8U4u5TjbD14mBO9bJkUfynVrHztBGtvYcMx2E5Yuyu/iiSRByJDFHfCINAY3+zvllkTipbPjTP5XlACJQIuRcw== - dependencies: - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/hash" "^5.7.0" - "@ethersproject/providers" "^5.7.2" - "@ethersproject/signing-key" "^5.7.0" - "@ethersproject/transactions" "^5.7.0" - "@ethersproject/wallet" "^5.7.0" - "@irys/arweave" "^0.0.2" - "@noble/ed25519" "^1.6.1" - base64url "^3.0.1" - bs58 "^4.0.1" - keccak "^3.0.2" - secp256k1 "^5.0.0" - optionalDependencies: - "@randlabs/myalgo-connect" "^1.1.2" - algosdk "^1.13.1" - arweave-stream-tx "^1.1.0" - multistream "^4.1.0" - tmp-promise "^3.0.2" - archiver-utils@^5.0.0, archiver-utils@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-5.0.2.tgz#63bc719d951803efc72cf961a56ef810760dd14d" @@ -637,13 +626,6 @@ async-lock@^1.4.1: resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.4.1.tgz#56b8718915a9b68b10fce2f2a9a3dddf765ef53f" integrity sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ== -async-retry@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" - integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== - dependencies: - retry "0.13.1" - async@^3.2.3, async@^3.2.4: version "3.2.6" resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" @@ -661,19 +643,10 @@ axios-retry@^4.3.0: dependencies: is-retry-allowed "^2.2.0" -axios@1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.3.tgz#a1125f2faf702bc8e8f2104ec3a76fab40257d85" - integrity sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw== - dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - -axios@^1.4.0: - version "1.7.5" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.5.tgz#21eed340eb5daf47d29b6e002424b3e88c8c54b1" - integrity sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw== +axios@1.7.7: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" @@ -752,7 +725,7 @@ bech32@1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -bignumber.js@^9.0.0, bignumber.js@^9.0.2, bignumber.js@^9.1.1: +bignumber.js@^9.0.0, bignumber.js@^9.0.2: version "9.1.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== @@ -1513,11 +1486,6 @@ readdir-glob@^1.1.2: dependencies: minimatch "^5.1.0" -retry@0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" From f6c6b1c6116adb5bf4cddacb2539581b44579b0c Mon Sep 17 00:00:00 2001 From: Ariel Melendez Date: Mon, 21 Oct 2024 08:55:44 -0700 Subject: [PATCH 38/41] feat(events): don't lose global tracker states on deploy PE-6796 --- src/main.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main.lua b/src/main.lua index cac0d83..65c2b6b 100644 --- a/src/main.lua +++ b/src/main.lua @@ -86,11 +86,11 @@ local ActionMap = { } -- Low fidelity trackers -local lastKnownCirculatingSupply = 0 -local lastKnownLockedSupply = 0 -local lastKnownStakedSupply = 0 -local lastKnownDelegatedSupply = 0 -local lastKnownWithdrawSupply = 0 +local lastKnownCirculatingSupply = lastKnownCirculatingSupply or 0 +local lastKnownLockedSupply = lastKnownLockedSupply or 0 +local lastKnownStakedSupply = lastKnownStakedSupply or 0 +local lastKnownDelegatedSupply = lastKnownDelegatedSupply or 0 +local lastKnownWithdrawSupply = lastKnownWithdrawSupply or 0 local function lastKnownTotalTokenSupply() return lastKnownCirculatingSupply + lastKnownLockedSupply From 64caf533747cb353ea07134a7f064e52102b0347 Mon Sep 17 00:00:00 2001 From: Ariel Melendez Date: Mon, 21 Oct 2024 15:28:51 -0700 Subject: [PATCH 39/41] feat(events): send all supply data when necessary PE-6796 --- src/main.lua | 87 +++++++++++++++++++-------------------------- tests/arns.test.mjs | 5 +++ 2 files changed, 42 insertions(+), 50 deletions(-) diff --git a/src/main.lua b/src/main.lua index 65c2b6b..e16a88c 100644 --- a/src/main.lua +++ b/src/main.lua @@ -128,6 +128,17 @@ local function addRecordResultFields(ioEvent, result) end end +local function addSupplyData(ioEvent, supplyData) + supplyData = supplyData or {} + ioEvent:addField("Circulating-Supply", supplyData.circulatingSupply or lastKnownCirculatingSupply) + ioEvent:addField("Locked-Supply", supplyData.lockedSupply or lastKnownLockedSupply) + ioEvent:addField("Staked-Supply", supplyData.stakedSupply or lastKnownStakedSupply) + ioEvent:addField("Delegated-Supply", supplyData.delegatedSupply or lastKnownDelegatedSupply) + ioEvent:addField("Withdraw-Supply", supplyData.withdrawSupply or lastKnownWithdrawSupply) + ioEvent:addField("Total-Token-Supply", supplyData.totalTokenSupply or lastKnownTotalTokenSupply()) + ioEvent:addField("Protocol-Balance", Balances[Protocol]) +end + local function gatewayStats() local numJoinedGateways = 0 local numLeavingGateways = 0 @@ -258,26 +269,16 @@ end, function(msg) end end - if lastKnownCirculatingSupply ~= previousState.lastKnownCirculatingSupply then - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) - end - if lastKnownLockedSupply ~= previousState.lastKnownLockedSupply then - msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) - end - if lastKnownStakedSupply ~= previousState.lastKnownStakedSupply then - msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) - end - if lastKnownDelegatedSupply ~= previousState.lastKnownDelegatedSupply then - msg.ioEvent:addField("Delegated-Supply", lastKnownDelegatedSupply) - end - if lastKnownWithdrawSupply ~= previousState.lastKnownWithdrawSupply then - msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) - end - if Balances[Protocol] ~= previousState.Balances[Protocol] then - msg.ioEvent:addField("Protocol-Balance", Balances[Protocol]) - end - if lastKnownTotalTokenSupply() ~= previousState.lastKnownTotalSupply then - msg.ioEvent:addField("Total-Token-Supply", lastKnownTotalTokenSupply()) + if + lastKnownCirculatingSupply ~= previousState.lastKnownCirculatingSupply + or lastKnownLockedSupply ~= previousState.lastKnownLockedSupply + or lastKnownStakedSupply ~= previousState.lastKnownStakedSupply + or lastKnownDelegatedSupply ~= previousState.lastKnownDelegatedSupply + or lastKnownWithdrawSupply ~= previousState.lastKnownWithdrawSupply + or Balances[Protocol] ~= previousState.Balances[Protocol] + or lastKnownTotalTokenSupply() ~= previousState.lastKnownTotalSupply + then + addSupplyData(msg.ioEvent) end return status @@ -421,8 +422,7 @@ addEventingHandler(ActionMap.CreateVault, utils.hasMatchingTag("Action", ActionM lastKnownLockedSupply = lastKnownLockedSupply + quantity lastKnownCirculatingSupply = lastKnownCirculatingSupply - quantity - msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + addSupplyData(msg.ioEvent) ao.send({ Target = from, @@ -485,8 +485,7 @@ addEventingHandler(ActionMap.VaultedTransfer, utils.hasMatchingTag("Action", Act lastKnownLockedSupply = lastKnownLockedSupply + quantity lastKnownCirculatingSupply = lastKnownCirculatingSupply - quantity - msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + addSupplyData(msg.ioEvent) -- sender gets an immediate debit notice as the quantity is debited from their balance ao.send({ @@ -599,8 +598,7 @@ addEventingHandler(ActionMap.IncreaseVault, utils.hasMatchingTag("Action", Actio lastKnownLockedSupply = lastKnownLockedSupply + quantity lastKnownCirculatingSupply = lastKnownCirculatingSupply - quantity - msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + addSupplyData(msg.ioEvent) ao.send({ Target = msg.From, @@ -666,7 +664,7 @@ addEventingHandler(ActionMap.BuyRecord, utils.hasMatchingTag("Action", ActionMap record = result.record addRecordResultFields(msg.ioEvent, result) lastKnownCirculatingSupply = lastKnownCirculatingSupply - record.purchasePrice - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + addSupplyData(msg.ioEvent) end msg.ioEvent:addField("Records-Count", utils.lengthOfTable(NameRegistry.records)) @@ -715,7 +713,7 @@ addEventingHandler(ActionMap.ExtendLease, utils.hasMatchingTag("Action", ActionM addRecordResultFields(msg.ioEvent, result) msg.ioEvent:addField("totalExtensionFee", recordResult.totalExtensionFee) lastKnownCirculatingSupply = lastKnownCirculatingSupply - recordResult.totalExtensionFee - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + addSupplyData(msg.ioEvent) end ao.send({ @@ -776,7 +774,7 @@ addEventingHandler( msg.ioEvent:addField("previousUndernameLimit", recordResult.undernameLimit - tonumber(msg.Tags.Quantity)) msg.ioEvent:addField("additionalUndernameCost", recordResult.additionalUndernameCost) lastKnownCirculatingSupply = lastKnownCirculatingSupply - recordResult.additionalUndernameCost - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) + addSupplyData(msg.ioEvent) end ao.send({ @@ -922,8 +920,7 @@ addEventingHandler(ActionMap.JoinNetwork, utils.hasMatchingTag("Action", ActionM lastKnownCirculatingSupply = lastKnownCirculatingSupply - stake lastKnownStakedSupply = lastKnownStakedSupply + stake - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) - msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + addSupplyData(msg.ioEvent) ao.send({ Target = fromAddress, @@ -986,8 +983,7 @@ addEventingHandler(ActionMap.LeaveNetwork, utils.hasMatchingTag("Action", Action lastKnownStakedSupply = lastKnownStakedSupply - gwPrevStake - gwPrevTotalDelegatedStake lastKnownWithdrawSupply = lastKnownWithdrawSupply + gwPrevStake + gwPrevTotalDelegatedStake - msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) - msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) + addSupplyData(msg.ioEvent) ao.send({ Target = msg.From, @@ -1040,8 +1036,7 @@ addEventingHandler( lastKnownCirculatingSupply = lastKnownCirculatingSupply - quantity lastKnownStakedSupply = lastKnownStakedSupply + quantity - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) - msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + addSupplyData(msg.ioEvent) ao.send({ Target = msg.From, @@ -1106,8 +1101,7 @@ addEventingHandler( lastKnownStakedSupply = lastKnownStakedSupply - quantity lastKnownWithdrawSupply = lastKnownWithdrawSupply + quantity - msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) - msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) + addSupplyData(msg.ioEvent) ao.send({ Target = msg.From, @@ -1164,8 +1158,7 @@ addEventingHandler(ActionMap.DelegateStake, utils.hasMatchingTag("Action", Actio lastKnownCirculatingSupply = lastKnownCirculatingSupply - quantity lastKnownStakedSupply = lastKnownStakedSupply + quantity - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) - msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) + addSupplyData(msg.ioEvent) ao.send({ Target = msg.From, @@ -1225,8 +1218,7 @@ addEventingHandler( lastKnownStakedSupply = lastKnownStakedSupply + vaultBalance lastKnownWithdrawSupply = lastKnownWithdrawSupply - vaultBalance - msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) - msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) + addSupplyData(msg.ioEvent) end end @@ -1377,8 +1369,7 @@ addEventingHandler( lastKnownStakedSupply = lastKnownStakedSupply - quantity lastKnownWithdrawSupply = lastKnownWithdrawSupply + quantity - msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) - msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) + addSupplyData(msg.ioEvent) ao.send({ Target = from, @@ -1595,14 +1586,10 @@ addEventingHandler("totalTokenSupply", utils.hasMatchingTag("Action", "Total-Tok lastKnownDelegatedSupply = delegatedSupply lastKnownWithdrawSupply = withdrawSupply - msg.ioEvent:addField("Total-Token-Supply", totalSupply) + addSupplyData(msg.ioEvent, { + totalSupply = totalSupply, + }) msg.ioEvent:addField("Last-Known-Total-Token-Supply", lastKnownTotalTokenSupply()) - msg.ioEvent:addField("Circulating-Supply", lastKnownCirculatingSupply) - msg.ioEvent:addField("Locked-Supply", lastKnownLockedSupply) - msg.ioEvent:addField("Staked-Supply", lastKnownStakedSupply) - msg.ioEvent:addField("Delegated-Supply", lastKnownDelegatedSupply) - msg.ioEvent:addField("Withdraw-Supply", lastKnownWithdrawSupply) - msg.ioEvent:addField("Protocol-Balance", protocolBalance) ao.send({ Target = msg.From, diff --git a/tests/arns.test.mjs b/tests/arns.test.mjs index b3f62df..a703b0c 100644 --- a/tests/arns.test.mjs +++ b/tests/arns.test.mjs @@ -110,6 +110,11 @@ describe('ArNS', async () => { 'Reserved-Records-Count': 0, 'Remaining-Balance': expectedRemainingBalance[sender], 'Circulating-Supply': -600000000, // Artifact of starting out without initializing this properly + 'Total-Token-Supply': 50000000000000, // Artifact of starting out without initializing this properly + 'Staked-Supply': 0, // Artifact of starting out without initializing this properly + 'Delegated-Supply': 0, // Artifact of starting out without initializing this properly + 'Withdraw-Supply': 0, // Artifact of starting out without initializing this properly + 'Locked-Supply': 0, // Artifact of starting out without initializing this properly }); // fetch the record From 6418371381f19064ecd7b902010671bbaab66483 Mon Sep 17 00:00:00 2001 From: Ariel Melendez Date: Mon, 21 Oct 2024 18:53:41 -0700 Subject: [PATCH 40/41] feat(events): send correct total supply PE-6796 --- src/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.lua b/src/main.lua index e16a88c..2041a4a 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1587,7 +1587,7 @@ addEventingHandler("totalTokenSupply", utils.hasMatchingTag("Action", "Total-Tok lastKnownWithdrawSupply = withdrawSupply addSupplyData(msg.ioEvent, { - totalSupply = totalSupply, + totalTokenSupply = totalSupply, }) msg.ioEvent:addField("Last-Known-Total-Token-Supply", lastKnownTotalTokenSupply()) From 46a1fc988a33462360e7dcf9e2ec5da0c199f0ea Mon Sep 17 00:00:00 2001 From: Ariel Melendez Date: Mon, 21 Oct 2024 20:44:15 -0700 Subject: [PATCH 41/41] feat(events): add withdraw supply to total supply PE-6796 --- src/main.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.lua b/src/main.lua index 2041a4a..db4853b 100644 --- a/src/main.lua +++ b/src/main.lua @@ -96,6 +96,7 @@ local function lastKnownTotalTokenSupply() + lastKnownLockedSupply + lastKnownStakedSupply + lastKnownDelegatedSupply + + lastKnownWithdrawSupply + Balances[Protocol] end