From e15c8a365f05c88a77606070f654cd54b7734fe9 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 28 Jan 2025 18:42:42 +0800 Subject: [PATCH 1/3] Create new token and add migration feature --- lua/tokenV2.lua | 311 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 lua/tokenV2.lua diff --git a/lua/tokenV2.lua b/lua/tokenV2.lua new file mode 100644 index 0000000..5576b05 --- /dev/null +++ b/lua/tokenV2.lua @@ -0,0 +1,311 @@ +local bint = require('.bint')(256) +--[[ + This module implements the ao Standard Token Specification. + + Terms: + Sender: the wallet or Process that sent the Message + + It will first initialize the internal state, and then attach handlers, + according to the ao Standard Token Spec API: + + - Info(): return the token parameters, like Name, Ticker, Logo, and Denomination + + - Balance(Target?: string): return the token balance of the Target. If Target is not provided, the Sender + is assumed to be the Target + + - Balances(): return the token balance of all participants + + - Transfer(Target: string, Quantity: number): if the Sender has a sufficient balance, send the specified Quantity + to the Target. It will also issue a Credit-Notice to the Target and a Debit-Notice to the Sender + + - Mint(Quantity: number): if the Sender matches the Process Owner, then mint the desired Quantity of tokens, adding + them the Processes' balance +]] +-- +local json = require('json') + +--[[ + utils helper functions to remove the bint complexity. +]] +-- + + +local utils = { + add = function(a, b) + return tostring(bint(a) + bint(b)) + end, + subtract = function(a, b) + return tostring(bint(a) - bint(b)) + end, + toBalanceValue = function(a) + return tostring(bint(a)) + end, + toNumber = function(a) + return bint.tonumber(a) + end +} + + +--[[ + Initialize State + + ao.id is equal to the Process.Id + ]] +-- +Variant = "0.0.3" + +-- token should be idempotent and not change previous state updates +Denomination = Denomination or 12 +Balances = Balances or { [ao.id] = utils.toBalanceValue(1000000 * 10 ^ Denomination) } +TotalSupply = TotalSupply or utils.toBalanceValue(1000000 * 10 ^ Denomination) +-- Name = Name or 'DUMPET' +if Name ~= 'DUMPET' then Name = 'DUMPET' end +Ticker = Ticker or 'DUMPET' +Logo = Logo or 'QifSiTK9jez7w1km9r9QM7BdNM_ddhyjK0g-gD2hKn0' + +--[[ + Add handlers for each incoming Action defined by the ao Standard Token Specification + ]] +-- + +--[[ + Info + ]] +-- +Handlers.add('info', Handlers.utils.hasMatchingTag("Action", "Info"), function(msg) + if msg.reply then + msg.reply({ + Name = Name, + Ticker = Ticker, + Logo = Logo, + Denomination = tostring(Denomination) + }) + else + Send({Target = msg.From, + Name = Name, + Ticker = Ticker, + Logo = Logo, + Denomination = tostring(Denomination) + }) + end +end) + +--[[ + Balance + ]] +-- +Handlers.add('balance', Handlers.utils.hasMatchingTag("Action", "Balance"), function(msg) + local bal = '0' + + -- If not Recipient is provided, then return the Senders balance + if (msg.Tags.Recipient) then + if (Balances[msg.Tags.Recipient]) then + bal = Balances[msg.Tags.Recipient] + end + elseif msg.Tags.Target and Balances[msg.Tags.Target] then + bal = Balances[msg.Tags.Target] + elseif Balances[msg.From] then + bal = Balances[msg.From] + end + if msg.reply then + msg.reply({ + Balance = bal, + Ticker = Ticker, + Account = msg.Tags.Recipient or msg.From, + Data = bal + }) + else + Send({ + Target = msg.From, + Balance = bal, + Ticker = Ticker, + Account = msg.Tags.Recipient or msg.From, + Data = bal + }) + end +end) + +--[[ + Balances + ]] +-- +Handlers.add('balances', Handlers.utils.hasMatchingTag("Action", "Balances"), + function(msg) + if msg.reply then + msg.reply({ Data = json.encode(Balances) }) + else + Send({Target = msg.From, Data = json.encode(Balances) }) + end + end) + +--[[ + Transfer + ]] +-- +Handlers.add('transfer', Handlers.utils.hasMatchingTag("Action", "Transfer"), function(msg) + assert(type(msg.Recipient) == 'string', 'Recipient is required!') + assert(type(msg.Quantity) == 'string', 'Quantity is required!') + assert(bint.__lt(0, bint(msg.Quantity)), 'Quantity must be greater than 0') + + if not Balances[msg.From] then Balances[msg.From] = "0" end + if not Balances[msg.Recipient] then Balances[msg.Recipient] = "0" end + + if bint(msg.Quantity) <= bint(Balances[msg.From]) then + Balances[msg.From] = utils.subtract(Balances[msg.From], msg.Quantity) + Balances[msg.Recipient] = utils.add(Balances[msg.Recipient], msg.Quantity) + + --[[ + Only send the notifications to the Sender and Recipient + if the Cast tag is not set on the Transfer message + ]] + -- + if not msg.Cast then + -- Debit-Notice message template, that is sent to the Sender of the transfer + local debitNotice = { + Action = 'Debit-Notice', + Recipient = msg.Recipient, + Quantity = msg.Quantity, + Data = Colors.gray .. + "You transferred " .. + Colors.blue .. msg.Quantity .. Colors.gray .. " to " .. Colors.green .. msg.Recipient .. Colors.reset + } + -- Credit-Notice message template, that is sent to the Recipient of the transfer + local creditNotice = { + Target = msg.Recipient, + Action = 'Credit-Notice', + Sender = msg.From, + Quantity = msg.Quantity, + Data = Colors.gray .. + "You received " .. + Colors.blue .. msg.Quantity .. Colors.gray .. " from " .. Colors.green .. msg.From .. Colors.reset + } + + -- Add forwarded tags to the credit and debit notice messages + for tagName, tagValue in pairs(msg) do + -- Tags beginning with "X-" are forwarded + if string.sub(tagName, 1, 2) == "X-" then + debitNotice[tagName] = tagValue + creditNotice[tagName] = tagValue + end + end + + -- Send Debit-Notice and Credit-Notice + if msg.reply then + msg.reply(debitNotice) + else + debitNotice.Target = msg.From + Send(debitNotice) + end + Send(creditNotice) + end + else + if msg.reply then + msg.reply({ + Action = 'Transfer-Error', + ['Message-Id'] = msg.Id, + Error = 'Insufficient Balance!' + }) + else + Send({ + Target = msg.From, + Action = 'Transfer-Error', + ['Message-Id'] = msg.Id, + Error = 'Insufficient Balance!' + }) + end + end +end) + +--[[ + Total Supply + ]] +-- +Handlers.add('totalSupply', Handlers.utils.hasMatchingTag("Action","Total-Supply"), function(msg) + assert(msg.From ~= ao.id, 'Cannot call Total-Supply from the same process!') + if msg.reply then + msg.reply({ + Action = 'Total-Supply', + Data = TotalSupply, + Ticker = Ticker + }) + else + Send({ + Target = msg.From, + Action = 'Total-Supply', + Data = TotalSupply, + Ticker = Ticker + }) + end +end) + +--[[ + Burn +]] -- +Handlers.add('burn', Handlers.utils.hasMatchingTag("Action",'Burn'), function(msg) + assert(type(msg.Tags.Quantity) == 'string', 'Quantity is required!') + assert(bint(msg.Tags.Quantity) <= bint(Balances[msg.From]), 'Quantity must be less than or equal to the current balance!') + + Balances[msg.From] = utils.subtract(Balances[msg.From], msg.Tags.Quantity) + TotalSupply = utils.subtract(TotalSupply, msg.Tags.Quantity) + if msg.reply then + msg.reply({ + Data = Colors.gray .. "Successfully burned " .. Colors.blue .. msg.Tags.Quantity .. Colors.reset + }) + else + Send({Target = msg.From, Data = Colors.gray .. "Successfully burned " .. Colors.blue .. msg.Tags.Quantity .. Colors.reset }) + end +end) + +local function sendErrorMessage(msg, err, target) + local targetId = target or msg.From + ao.send({ Target = targetId, Error = "true", Data = err }) + print("Error! " .. "Target " .. " " .. targetId .. " " .. err) +end + +Handlers.add("Credit-Notice", Handlers.utils.hasMatchingTag("Action", "Credit-Notice"), function(msg) + if msg.From != 'QD3R6Qes15eQqIN_TK5s7ttawzAiX8ucYI2AUXnuS18' then + ao.send({ + Target = msg.From, -- user token PROCESS_ID + Action = "Transfer", + Recipient = msg.Tags.Sender, + Quantity = msg.Tags.Quantity, + }) + sendErrorMessage(msg, 'Invalid token received', msg.Tags.Sender) + return + end + + ao.send({ + Target = UserTxProcessId, + Action = "Upsert", + ProfileId = msg.Tags.Sender, + MarketProcessId = MarketInfo.ProcessId, + Title = MarketInfo + .Title + }) + + if msg.Tags.Sender == msg.From then + local currentVal = Balances[ao.id] or "0" + Balances[ao.id] = utils.add(currentVal, msg.Tags.Quantity) + printData("Balances[ao.id]", { + Action = "Credit-Notice", + Quantity = msg.Tags.Quantity, + From = msg.From, + Sender = msg.Tags.Sender, + BlockHeight = msg["Block-Height"], + Timestamp = msg["Timestamp"], + Balance = Balances[ao.id], + }) + else + local currentVal = Balances[msg.Tags.Sender] or "0" + Balances[msg.Tags.Sender] = utils.add(currentVal, msg.Tags.Quantity) + printData("Balances[msg.Tags.Sender]", { + Action = "Credit-Notice", + Quantity = msg.Tags.Quantity, + From = msg.From, + Sender = msg.Tags.Sender, + BlockHeight = msg["Block-Height"], + Timestamp = msg["Timestamp"], + Balance = Balances[ao.id], + }) + end +end) From 4c97c61cab87d86160e45688c1281922a4d858eb Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 28 Jan 2025 18:45:55 +0800 Subject: [PATCH 2/3] removed unrelated code --- lua/tokenV2.lua | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lua/tokenV2.lua b/lua/tokenV2.lua index 5576b05..fdf20ca 100644 --- a/lua/tokenV2.lua +++ b/lua/tokenV2.lua @@ -274,15 +274,6 @@ Handlers.add("Credit-Notice", Handlers.utils.hasMatchingTag("Action", "Credit-No return end - ao.send({ - Target = UserTxProcessId, - Action = "Upsert", - ProfileId = msg.Tags.Sender, - MarketProcessId = MarketInfo.ProcessId, - Title = MarketInfo - .Title - }) - if msg.Tags.Sender == msg.From then local currentVal = Balances[ao.id] or "0" Balances[ao.id] = utils.add(currentVal, msg.Tags.Quantity) From 90be8c914ce639aea9ae7c882a05c3bc51df741d Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 19 Feb 2025 01:46:39 +0800 Subject: [PATCH 3/3] add sender validation on upsert --- lua/usertx.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lua/usertx.lua b/lua/usertx.lua index 68f550e..28e164f 100644 --- a/lua/usertx.lua +++ b/lua/usertx.lua @@ -14,6 +14,12 @@ local function sendErrorMessage(msg, err, target) end Handlers.add("Upsert", Handlers.utils.hasMatchingTag("Action", "Upsert"), function(msg) + -- if Sender is not the process itself + if msg.Tags.Sender != msg.From then + sendErrorMessage(msg, 'Invalid Sender!') + return + end + local success, err = pcall(function() if type(msg.Tags.ProfileId) ~= 'string' or msg.Tags.ProfileId:match("^%s*$") then sendErrorMessage(msg, 'ProfileId is required and must be a non-empty string!')