Skip to content

Instantly share code, notes, and snippets.

@andytudhope
Last active March 7, 2024 06:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andytudhope/bca5c5b6f8e1b6863123e2007675c64c to your computer and use it in GitHub Desktop.
Save andytudhope/bca5c5b6f8e1b6863123e2007675c64c to your computer and use it in GitHub Desktop.
A better way to stake on memes
-- Key the Proposals table by the name so that any process can propose multiple handlers
Proposals = Proposals or {
["init"] = {
stake = 0,
pattern = "none",
handle = "test",
stakers = {} -- Track stakers and their individual stakes for later unstaking
}
}
-- Keep track of which community-proposed handlers are currently active
ActiveHandlers = ActiveHandlers or { }
-- So we can add an active handler to the process list
Handlers.list = Handlers.list or {}
-- The top five proposals are active at any given time, so long as each of them exceeds a given threshold of WHAT staked.
-- This pays homage to the slates of Maker's DSChief, and their hats and spells; to this day, an underrated voting methodology.
function loadHandlers()
-- Remove old handlers first
for _, name in ipairs(ActiveHandlers) do
local idx = findIndexByProp(Handlers.list, "name", name)
table.remove(Handlers.list, idx)
end
ActiveHandlers = {}
-- Sort the Proposals table by amount staked
local sortableProposals = {}
for name, details in pairs(Proposals) do
table.insert(sortableProposals, {name = name, stake = details.stake, pattern = details.pattern, handle = details.handle})
end
table.sort(sortableProposals, function(a, b) return a.stake > b.stake end)
local loadedCount = 0
for _, proposal in ipairs(sortableProposals) do
if loadedCount >= 5 then break end -- Stop after loading 5 proposals
-- Check if the proposal has more than 10 WHAT staked
if proposal.stake > 10000 then
assert(type(proposal.name) == 'string' and type(proposal.pattern) == 'function' and type(proposal.handle) == 'function', 'invalid arguments: handler.add(name : string, pattern : function(msg: Message) : {-1 = break, 0 = skip, 1 = continue}, handle(msg : Message) : void)')
assert(type(proposal.name) == 'string', 'name MUST be string')
assert(type(proposal.pattern) == 'function', 'pattern MUST be function')
assert(type(proposal.handle) == 'function', 'handle MUST be function')
table.insert(Handlers.list, { pattern = proposal.pattern, handle = proposal.handle, name = proposal.name })
table.insert(ActiveHandlers, proposal.name)
loadedCount = loadedCount + 1
end
end
end
function findIndexByProp(array, prop, value)
for index, object in ipairs(array) do
if object[prop] == value then
return index
end
end
return nil
end
-- expects a proposal in the form of
-- Send({ Target = WHAT, Action = "Propose", Tags = {Name = "ping", Pattern = "Handlers.utils.hasMatchingTag('Action', 'Ping')", Handle = "Handlers.utils.reply('pong')"} })
-- Will add this proposed Handler to the Proposals table.
Handlers.add(
"propose",
Handlers.utils.hasMatchingTag("Action", "Propose"),
function(m)
-- Validate that the Proposal contains necessary fields
if m.Tags.Name and m.Tags.Pattern and m.Tags.Handle then
-- Check if the name already exists in the Proposals to handle potential duplicates
local name = m.Tags.Name
if Proposals[name] then
local counter = 1
while Proposals[name .. "_" .. tostring(counter)] do
counter = counter + 1
end
name = name .. "_" .. tostring(counter)
end
-- Load Pattern as a function
local patternFunc, patternErr = load(m.Tags.Pattern, "aoWifHat", "t", _G)
if patternErr then
print("Failed to load pattern.")
return
end
-- Load Handle as a function
local handleFunc, handleErr = load(m.Tags.Handle, "aoWifHat", "t", _G)
if handleErr then
print("Failed to load handle.")
return
end
-- Insert the new proposal into the Proposals table
Proposals[name] = {
stake = 0, -- Starts with 0 stake
pattern = patternFunc,
handle = handleFunc,
stakers = {}
}
ao.send({ Target = m.From, Data= "Proposal for " .. name .. " added." })
else
-- Invalid proposal format
ao.send({ Target = m.From, Data= "Invalid proposal submitted." })
end
end
)
-- Rather than actually sending the balance anywhere, we just account for it
-- directly in the Proposals table, which enables per proposal unstaking later
Handlers.add(
"stake",
Handlers.utils.hasMatchingTag("Action", "Stake"),
function(m)
assert(type(m.Tags.Quantity) == 'string', 'Please specify how much you are staking')
assert(type(m.Tags.Name) == 'string', 'Please name the proposal you are staking on')
if not Balances[m.From] then Balances[m.From] = 0 end
if not Proposals[m.Tags.Name] then
ao.send({ Target = m.From, Data = "That proposal does not exist"})
return
end
local qty = tonumber(m.Tags.Quantity)
assert(type(qty) == 'number', 'Quantity Tag must be a number')
if Balances[m.From] >= qty then
Balances[m.From] = Balances[m.From] - qty
-- Initialize or update the stake for the current process ID
Proposals[m.Tags.Name].stakers[m.From] = (Proposals[m.Tags.Name].stakers[m.From] or 0) + qty
-- Update the total stake for the proposal
Proposals[m.Tags.Name].stake = Proposals[m.Tags.Name].stake + qty
ao.send({ Target = m.From, Data = "Your stake has been added to the " .. m.Tags.Name .. " proposal. Your WHAT balance is now: " .. Balances[m.From] })
loadHandlers()
else
ao.send({
Target = m.From,
Tags = { Action = 'Transfer-Error', ['Message-Id'] = m.Id, Error = 'Insufficient Balance!' }
})
end
end
)
Handlers.add(
"unstake",
Handlers.utils.hasMatchingTag("Action", "Unstake"),
function(m)
assert(type(m.Tags.Quantity) == 'string', 'Please specify how much you are unstaking')
assert(type(m.Tags.Name) == 'string', 'Please name the proposal you are unstaking from')
local qty = tonumber(m.Tags.Quantity)
assert(type(qty) == 'number', 'Quantity Tag must be a number')
if Proposals[m.Tags.Name].stakers[m.From] >= qty then
Balances[m.From] = Balances[m.From] + qty
Proposals[m.Tags.Name].stakers[m.From] = Proposals[m.Tags.Name].stakers[m.From] - qty
Proposals[m.Tags.Name].stake = Proposals[m.Tags.Name].stake - qty
ao.send({ Target = m.From, Data = "Your stake has been removed from the " .. m.Tags.Name .. " proposal. Your WHAT balance is now: " .. Balances[m.From] })
else
ao.send({ Target = m.From, Data = "You do not have the quantity of WHAT staked on this proposal you have attempted to unstake." })
end
end
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment