diff --git a/spec/gar_spec.lua b/spec/gar_spec.lua index 48741a9..a83a013 100644 --- a/spec/gar_spec.lua +++ b/spec/gar_spec.lua @@ -1075,8 +1075,8 @@ describe("gar", function() it("should decrease delegated stake with instant withdrawal and apply penalty and remove delegate", function() Balances[ao.id] = 0 - local expenditedWithdrawalFee = 1000 * 0.80 - local withdrawalAmount = 1000 - expenditedWithdrawalFee + local expeditedWithdrawalFee = 1000 * 0.80 + local withdrawalAmount = 1000 - expeditedWithdrawalFee GatewayRegistry[stubGatewayAddress] = { operatorStake = gar.getSettings().operators.minStake, totalDelegatedStake = gar.getSettings().delegates.minStake + 1000, @@ -1117,7 +1117,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(expenditedWithdrawalFee, Balances[ao.id]) + assert.are.equal(expeditedWithdrawalFee, Balances[ao.id]) assert.are.equal( gar.getSettings().delegates.minStake, _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake @@ -1178,9 +1178,9 @@ describe("gar", function() local currentTimestamp = 1000000 local startTimestamp = 1000000 local vaultBalance = 1000 - local expectedPenaltyRate = constants.MAX_EXPEDITED_WITHDRAWAL_FEE - local expectedexpenditedWithdrawalFee = vaultBalance * expectedPenaltyRate - local expectedWithdrawalAmount = vaultBalance - expectedexpenditedWithdrawalFee + local expectedPenaltyRate = constants.MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE + local expectedexpeditedWithdrawalFee = vaultBalance * expectedPenaltyRate + local expectedWithdrawalAmount = vaultBalance - expectedexpeditedWithdrawalFee Balances[ao.id] = 0 @@ -1196,6 +1196,7 @@ describe("gar", function() [vaultId] = { balance = vaultBalance, startTimestamp = startTimestamp, + endTimestamp = startTimestamp + gar.getSettings().delegates.withdrawLengthMs, }, }, }, @@ -1224,10 +1225,16 @@ describe("gar", function() ) 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.same({ + delegate = nil, -- Delegate should be removed after full withdrawal + elapsedTime = 0, + remainingTime = gar.getSettings().delegates.withdrawLengthMs, + penaltyRate = 0.8, + expeditedWithdrawalFee = 800, + amountWithdrawn = 200, + }, result) assert.are.equal(expectedWithdrawalAmount, Balances[stubRandomAddress]) - assert.are.equal(expectedexpenditedWithdrawalFee, Balances[ao.id]) + assert.are.equal(expectedexpeditedWithdrawalFee, Balances[ao.id]) assert.are.equal(0, _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake) end ) @@ -1242,13 +1249,15 @@ describe("gar", function() local elapsedTime = 15 * 24 * 60 * 60 * 1000 -- Half of 30 days in milliseconds local currentTimestamp = startTimestamp + elapsedTime local vaultBalance = 1000 - local penaltyRate = constants.MAX_EXPEDITED_WITHDRAWAL_FEE + local penaltyRate = constants.MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE - ( - (constants.MAX_EXPEDITED_WITHDRAWAL_FEE - constants.MIN_EXPEDITED_WITHDRAWAL_FEE) - * (elapsedTime / gar.getSettings().delegates.withdrawLengthMs) + ( + constants.MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE + - constants.MIN_EXPEDITED_WITHDRAWAL_PENALTY_RATE + ) * (elapsedTime / gar.getSettings().delegates.withdrawLengthMs) ) - local expectedexpenditedWithdrawalFee = math.floor(vaultBalance * penaltyRate) - local expectedWithdrawalAmount = vaultBalance - expectedexpenditedWithdrawalFee + local expectedexpeditedWithdrawalFee = math.floor(vaultBalance * penaltyRate) + local expectedWithdrawalAmount = vaultBalance - expectedexpeditedWithdrawalFee Balances[ao.id] = 0 _G.GatewayRegistry[stubGatewayAddress] = { @@ -1263,6 +1272,7 @@ describe("gar", function() [vaultId] = { balance = vaultBalance, startTimestamp = startTimestamp, + endTimestamp = startTimestamp + gar.getSettings().delegates.withdrawLengthMs, }, }, }, @@ -1291,10 +1301,21 @@ describe("gar", function() ) assert.is_true(status) - assert.are.equal(nil, next(result.delegate.vaults)) -- Delegate should have no vaults remaining - assert.are.equal(remainingDelegateStakeBalance, result.totalDelegatedStake) + result.penaltyRate = string.format("%.3f", result.penaltyRate) -- stave off floating point errors + assert.are.same({ + delegate = { + delegatedStake = 1000, + startTimestamp = 500000, + vaults = {}, -- Delegate should have no vaults remaining + }, + elapsedTime = 1296000000, + remainingTime = 1296000000, + penaltyRate = "0.425", + expeditedWithdrawalFee = 425, + amountWithdrawn = 575, + }, result) assert.are.equal(expectedWithdrawalAmount, Balances[stubRandomAddress]) - assert.are.equal(expectedexpenditedWithdrawalFee, Balances[ao.id]) + assert.are.equal(expectedexpeditedWithdrawalFee, Balances[ao.id]) assert.are.equal( remainingDelegateStakeBalance, _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake @@ -1303,21 +1324,23 @@ describe("gar", function() ) it( - "should withdraw delegate stake and apply reduced penalty based on more elapsed time and remove delegate", + "should withdraw delegate stake and apply near minimum penalty based nearly all required 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 elapsedTime = 30 * 24 * 60 * 60 * 1000 - 1 -- 1ms less than 30 days in milliseconds local currentTimestamp = startTimestamp + elapsedTime - local penaltyRate = constants.MAX_EXPEDITED_WITHDRAWAL_FEE + local penaltyRate = constants.MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE - ( - (constants.MAX_EXPEDITED_WITHDRAWAL_FEE - constants.MIN_EXPEDITED_WITHDRAWAL_FEE) - * (elapsedTime / gar.getSettings().delegates.withdrawLengthMs) + ( + constants.MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE + - constants.MIN_EXPEDITED_WITHDRAWAL_PENALTY_RATE + ) * (elapsedTime / gar.getSettings().delegates.withdrawLengthMs) ) - local expectedexpenditedWithdrawalFee = math.floor(vaultBalance * penaltyRate) - local expectedWithdrawalAmount = vaultBalance - expectedexpenditedWithdrawalFee + local expectedexpeditedWithdrawalFee = math.floor(vaultBalance * penaltyRate) + local expectedWithdrawalAmount = vaultBalance - expectedexpeditedWithdrawalFee Balances[ao.id] = 0 _G.GatewayRegistry[stubGatewayAddress] = { @@ -1332,6 +1355,7 @@ describe("gar", function() [vaultId] = { balance = vaultBalance, startTimestamp = startTimestamp, + endTimestamp = startTimestamp + gar.getSettings().delegates.withdrawLengthMs, }, }, }, @@ -1360,10 +1384,17 @@ describe("gar", function() ) assert.is_true(status) - assert.are.equal(nil, result.delegate) -- Delegate should be removed after full withdrawal - assert.are.equal(0, result.totalDelegatedStake) + result.penaltyRate = string.format("%.3f", result.penaltyRate) -- stave off floating point errors + assert.are.same({ + delegate = nil, -- Delegate should be removed after full withdrawal + elapsedTime = 2591999999, + remainingTime = 1, + penaltyRate = "0.050", + expeditedWithdrawalFee = 50, + amountWithdrawn = 950, + }, result) assert.are.equal(expectedWithdrawalAmount, Balances[stubRandomAddress]) - assert.are.equal(expectedexpenditedWithdrawalFee, Balances[ao.id]) + assert.are.equal(expectedexpeditedWithdrawalFee, Balances[ao.id]) assert.are.equal(0, _G.GatewayRegistry[stubGatewayAddress].totalDelegatedStake) end ) @@ -1386,6 +1417,7 @@ describe("gar", function() [vaultId] = { balance = vaultBalance, startTimestamp = startTimestamp, + endTimestamp = startTimestamp + gar.getSettings().delegates.withdrawLengthMs, }, }, }, diff --git a/src/constants.lua b/src/constants.lua index 9066ac5..656f7cf 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_EXPEDITED_WITHDRAWAL_FEE = 0.05 -constants.MAX_EXPEDITED_WITHDRAWAL_FEE = 0.80 +constants.MIN_EXPEDITED_WITHDRAWAL_PENALTY_RATE = 0.05 +constants.MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE = 0.80 -- ARNS constants.DEFAULT_UNDERNAME_COUNT = 10 diff --git a/src/gar.lua b/src/gar.lua index ed531dc..c23ee35 100644 --- a/src/gar.lua +++ b/src/gar.lua @@ -380,11 +380,11 @@ function gar.decreaseDelegateStake(gatewayAddress, delegator, qty, currentTimest gateway.totalDelegatedStake = gateway.totalDelegatedStake - qty -- Calculate the penalty amount - local expenditedWithdrawalFee = qty * constants.MAX_EXPEDITED_WITHDRAWAL_FEE - local amountToWithdraw = qty - expenditedWithdrawalFee + local expeditedWithdrawalFee = qty * constants.MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE + local amountToWithdraw = qty - expeditedWithdrawalFee -- Add penalty to AR.IO protocol balance - balances.increaseBalance(ao.id, expenditedWithdrawalFee) + balances.increaseBalance(ao.id, expeditedWithdrawalFee) -- Withdraw the remaining tokens to the delegate balances.increaseBalance(delegator, amountToWithdraw) @@ -809,7 +809,7 @@ function gar.instantDelegateWithdrawal(from, gatewayAddress, vaultId, currentTim -- Calculate elapsed time since the withdrawal started local elapsedTime = currentTimestamp - vault.startTimestamp - local totalWithdrawalTime = gar.getSettings().delegates.withdrawLengthMs + local totalWithdrawalTime = vault.endTimestamp - vault.startTimestamp -- Ensure the elapsed time is not negative if elapsedTime < 0 then @@ -817,22 +817,22 @@ function gar.instantDelegateWithdrawal(from, gatewayAddress, vaultId, currentTim end -- Calculate the penalty rate based on elapsed time - 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 + local maxPenaltyRateReduction = constants.MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE + - constants.MIN_EXPEDITED_WITHDRAWAL_PENALTY_RATE + local earnedPenaltyRateReduction = maxPenaltyRateReduction * (elapsedTime / totalWithdrawalTime) + local penaltyRate = constants.MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE - earnedPenaltyRateReduction + penaltyRate = math.max( + constants.MIN_EXPEDITED_WITHDRAWAL_PENALTY_RATE, + math.min(constants.MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE, penaltyRate) + ) -- Ensure penalty is within bounds -- Calculate the penalty amount and the amount to withdraw local vaultBalance = vault.balance - - local expenditedWithdrawalFee = math.floor(vaultBalance * penaltyRate) - local amountToWithdraw = vaultBalance - expenditedWithdrawalFee + local expeditedWithdrawalFee = math.floor(vaultBalance * penaltyRate) + local amountToWithdraw = vaultBalance - expeditedWithdrawalFee -- Add penalty to AR.IO protocol balance - balances.increaseBalance(ao.id, expenditedWithdrawalFee) + balances.increaseBalance(ao.id, expeditedWithdrawalFee) balances.increaseBalance(from, amountToWithdraw) -- Remove the vault after withdrawal @@ -847,7 +847,11 @@ function gar.instantDelegateWithdrawal(from, gatewayAddress, vaultId, currentTim GatewayRegistry[gatewayAddress] = gateway return { delegate = gar.getGateway(gatewayAddress).delegates[from], - totalDelegatedStake = gateway.totalDelegatedStake, + elapsedTime = elapsedTime, + remainingTime = totalWithdrawalTime - elapsedTime, + penaltyRate = penaltyRate, + expeditedWithdrawalFee = expeditedWithdrawalFee, + amountWithdrawn = amountToWithdraw, } end diff --git a/src/main.lua b/src/main.lua index a6c521c..c52b410 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1317,17 +1317,24 @@ addEventingHandler( return end - local delegateResult = {} + local delegateResult = result and result.delegate or { + delegatedStake = 0, + vaults = {}, + } + msg.ioEvent:addField("Remaining-Delegate-Stake", delegateResult.delegatedStake) 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 + msg.ioEvent:addField("Vault-Elapsed-Time", result.elapsedTime) + msg.ioEvent:addField("Vault-Remaining-Time", result.remainingTime) + msg.ioEvent:addField("Penalty-Rate", result.penaltyRate) + msg.ioEvent:addField("Instant-Withdrawal-Fee", result.expeditedWithdrawalFee) + msg.ioEvent:addField("Amount-Withdrawn", result.amountWithdrawn) + msg.ioEvent:addField("Previous-Vault-Balance", result.amountWithdrawn + result.expeditedWithdrawalFee) + lastKnownCirculatingSupply = lastKnownCirculatingSupply + result.amountWithdrawn + lastKnownWithdrawSupply = lastKnownWithdrawSupply - result.amountWithdrawn - result.expeditedWithdrawalFee end + addSupplyData(msg.ioEvent) + ao.send({ Target = msg.From, Tags = { @@ -1390,13 +1397,22 @@ addEventingHandler( return end + local instantWithdrawalFee = 0 + local amountWithdrawn = 0 + if instantWithdraw then + instantWithdrawalFee = quantity * constants.MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE + amountWithdrawn = quantity - instantWithdrawalFee + msg.ioEvent:addField("Instant-Withdrawal", instantWithdraw) + msg.ioEvent:addField("Instant-Withdrawal-Fee", instantWithdrawalFee) + msg.ioEvent:addField("Amount-Withdrawn", amountWithdrawn) + end + local delegateResult = {} if gateway ~= nil then local newStake = gateway.delegates[from].delegatedStake 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 @@ -1411,8 +1427,11 @@ addEventingHandler( end end - lastKnownStakedSupply = lastKnownStakedSupply - quantity - lastKnownWithdrawSupply = lastKnownWithdrawSupply + quantity + lastKnownDelegatedSupply = lastKnownDelegatedSupply - quantity + if not instantWithdraw then + lastKnownWithdrawSupply = lastKnownWithdrawSupply + quantity + end + lastKnownCirculatingSupply = lastKnownCirculatingSupply + amountWithdrawn addSupplyData(msg.ioEvent) ao.send({