Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Draft: Subledgers implementation #285

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
316 changes: 316 additions & 0 deletions blueprints/subledger_code.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
local subledgerCode = [===[

local bint = require('.bint')(256)
local ao = require('ao')
--[[
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 tonumber(a)
end
}

--[[
Initialize State

ao.id is equal to the Process.Id
]]
--
Variant = "0.0.3"

-- token state should be assigned from the spawning message
-- token should be idempotent and not change previous state updates
Name = Name or ao.env.Process.Tags['Token-Name']
Ticker = Ticker or ao.env.Process.Tags['Token-Ticker']
Denomination = Denomination or tonumber(ao.env.Process.Tags['Token-Denomination']) or 12
Logo = Logo or ao.env.Process.Tags['Token-Logo']

if not ao.env.Process.Tags['Parent-Token'] then
local initialSupply = tonumber(ao.env.Process.Tags['Token-Supply']) or 10000
local initialBalance = utils.toBalanceValue(initialSupply * 10 ^ Denomination)

Balances = Balances or { [ao.id] = initialBalance }
TotalSupply = TotalSupply or initialBalance
else
Balances = Balances or {}
end

SourceToken = ao.env.Process.Tags['Source-Token'] or ao.id
ParentToken = ao.env.Process.Tags['Parent-Token']

Subledgers = Subledgers or {}
SubledgersPendingInit = SubledgersPendingInit or {}

--[[
Add handlers for each incoming Action defined by the ao Standard Token Specification
]]
--

--[[
Info
]]
--
Handlers.add('info', Handlers.utils.hasMatchingTag('Action', 'Info'), function(msg)
ao.send({
Target = msg.From,
Name = Name,
Ticker = Ticker,
Logo = Logo,
Denomination = tostring(Denomination)
})
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

ao.send({
Target = msg.From,
Balance = bal,
Ticker = Ticker,
Account = msg.Tags.Recipient or msg.From,
Data = bal
})
end)

--[[
Balances
]]
--
Handlers.add('balances', Handlers.utils.hasMatchingTag('Action', 'Balances'),
function(msg) ao.send({ Target = msg.From, Data = json.encode(Balances) }) 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)

local SubledgerTags = {}
if Subledgers and Subledgers[msg.Recipient] then
SubledgerTags = {
['Source-Token'] = SourceToken or ao.id,
['Parent-Token'] = ParentToken,
}
end

--[[
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 = {
Target = msg.From,
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

-- Add SubledgerTags to the credit notice if applicable
for tagName, tagValue in pairs(SubledgerTags) do
creditNotice[tagName] = tagValue
end

-- Send Debit-Notice and Credit-Notice
ao.send(debitNotice)
ao.send(creditNotice)
end
else
ao.send({
Target = msg.From,
Action = 'Transfer-Error',
['Message-Id'] = msg.Id,
Error = 'Insufficient Balance!'
})
end
end)

--[[
Mint
]]
--
Handlers.add('mint', Handlers.utils.hasMatchingTag('Action', 'Mint'), function(msg)
assert(type(msg.Quantity) == 'string', 'Quantity is required!')
assert(bint(0) < bint(msg.Quantity), 'Quantity must be greater than zero!')

if not Balances[ao.id] then Balances[ao.id] = "0" end

if msg.From == ao.id then
-- Add tokens to the token pool, according to Quantity
Balances[msg.From] = utils.add(Balances[msg.From], msg.Quantity)
TotalSupply = utils.add(TotalSupply, msg.Quantity)
ao.send({
Target = msg.From,
Data = Colors.gray .. "Successfully minted " .. Colors.blue .. msg.Quantity .. Colors.reset
})
else
ao.send({
Target = msg.From,
Action = 'Mint-Error',
['Message-Id'] = msg.Id,
Error = 'Only the Process Id can mint new ' .. Ticker .. ' tokens!'
})
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!')

ao.send({
Target = msg.From,
Action = 'Total-Supply',
Data = TotalSupply,
Ticker = Ticker
})
end)

--[[
Burn
]] --
Handlers.add('burn', Handlers.utils.hasMatchingTag('Action', 'Burn'), function(msg)
assert(type(msg.Quantity) == 'string', 'Quantity is required!')
assert(bint(msg.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.Quantity)
TotalSupply = utils.subtract(TotalSupply, msg.Quantity)

ao.send({
Target = msg.From,
Data = Colors.gray .. "Successfully burned " .. Colors.blue .. msg.Quantity .. Colors.reset
})
end)

Handlers.add('creditNotice', Handlers.utils.hasMatchingTag('Action', 'Credit-Notice'), function(msg)
assert(type(msg.Quantity) == 'string', 'Quantity is required!')
assert(type(msg.Sender) == 'string', 'Sender is required!')

if msg.From == ParentToken then
if not Balances[msg.Sender] then Balances[msg.Sender] = "0" end
Balances[msg.Sender] = utils.add(Balances[msg.Sender], msg.Quantity)
ao.send({
Target = msg.Sender,
Data = Colors.gray .. "Successfully credited " .. Colors.blue .. msg.Quantity .. Colors.gray .. " to subledger " .. Colors.green .. ao.id .. Colors.reset
})
else
ao.send({
Target = msg.Sender,
Action = 'Credit-Error',
['Message-Id'] = msg.Id,
Error = 'Invalid Parent-Token!'
})
end
end)

Handlers.add('withdraw', Handlers.utils.hasMatchingTag('Action', 'Withdraw'), function(msg)
assert(type(ParentToken) == 'string', 'This process has no Parent')
assert(type(msg.Quantity) == 'string', 'Quantity is required!')
assert(bint(msg.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.Quantity)

local recipient = msg.From
if type(msg.Recipient) == 'string' then
recipient = msg.Recipient
end

ao.send({
Target = ParentToken,
Action = 'Transfer',
Recipient = recipient,
Quantity = msg.Quantity
})
end)

]===]

return subledgerCode
Loading