AMM 蓝图
AMM 蓝图为开发者提供了基于 AMM 的模块化模板,允许开发者实现自己的做市算法,或使用内置的两种算法:UniswapV2 曲线 和 恒定价格算法。以下是代码逻辑和功能的详细解析。
核心功能概述
1. 池管理
支持添加新池、和设置池的流动性。
每个池子可以基于不同的做市算法(如 UniswapV2、BigOrder)。
每个池子的状态都被抽象到 FFP.Pools 内进行单独维护。
2. 订单操作
支持用户通过池子完成交易(如兑换交易)
验证订单输入与输出的有效性。
创建满足池子价格的 Note 票据为订单服务。
3. 结算操作
执行 FFP 结算中心结算请求完成 Note 的最终结算。
维护订单的结算状态(如完成结算、过期、拒绝等)。
自动处理已过期或完成的票据。
4. 做市策略灵活性
使用 AlgoToPool 映射表为池子选择不同的做市算法。
开发者可以基于此框架实现新的做市算法。
代码详解
1. 池管理
创建池
code
Handlers.add('ffp.addPool', 'FFP.AddPool',
function(msg)
assert(msg.From == Owner or msg.From == ao.id, 'Only owner can add pool')
assert(type(msg.X) == 'string', 'X is required')
assert(type(msg.Y) == 'string', 'Y is required')
assert(msg.X < msg.Y, 'X must be less than Y')
assert(type(msg.Algo) == 'string', 'Algo is required')
assert(type(msg.Data) == 'string', 'Params is required')
local poolId = getPoolId(msg.X, msg.Y)
if FFP.Pools[poolId] then
msg.reply({Error = 'Pool already exists'})
return
end
local P = FFP.AlgoToPool[msg.Algo]
if not P then
msg.reply({Error = 'Unsupported amm algo'})
return
end
local pool = P.new(msg.X, msg.Y, json.decode(msg.Data))
if not pool then
msg.reply({Error = 'Invalid pool params'})
return
end
FFP.Pools[poolId] = pool
msg.reply({Action = 'PoolAdded-Notice', Pool = poolId})
Send({Target = FFP.Publisher, Action = 'PoolAdded-Notice', Creator = ao.id, Data = json.encode(pool)})
end
)
代码逻辑:
检查是否已有相同的池子存在。
指定做市算法创建池子。
将池子加入到 FFP.Pools。
池流动性设置
code
Handlers.add('ffp.updateLiquidity', 'FFP.UpdateLiquidity',
function(msg)
assert(msg.From == Owner or msg.From == ao.id, 'Only owner can update liquidity')
assert(type(msg.Y) == 'string', 'Y is required')
assert(msg.X < msg.Y, 'X must be less than Y')
assert(type(msg.Data) == 'string', 'Params is required')
local pool = getPool(msg.X, msg.Y)
if not pool then
msg.reply({Error = 'Pool not found'})
return
end
local ok = pool:updateLiquidity(json.decode(msg.Data))
if not ok then
msg.reply({Error = 'Invalid updateLiquidity params'})
return
end
msg.reply({Action = 'LiquidityUpdated-Notice', Data = json.encode(pool)})
end
)
代码逻辑:
查找指定池子,并调用池子对象的 updateLiquidity 方法。
更新完成后通知结果。
2. 订单操作
创建订单
code
Handlers.add('ffp.makeOrder', 'FFP.MakeOrder',
function(msg)
if FFP.Executing then
msg.reply({Error = 'err_executing'})
return
end
local ok, err = validateOrderMsg(msg)
if not ok then
msg.reply({Error = err})
return
end
local pool = getPool(msg.TokenIn, msg.TokenOut)
local tokenOut, amountOut = pool:getAmountOut(msg.TokenIn, msg.AmountIn)
if not amountOut or amountOut == '0' then
msg.reply({Error = 'err_no_amount_out'})
return
end
if msg.AmountOut then
local ok, err = validateAmountOut(pool, msg.TokenIn, msg.AmountIn, msg.TokenOut, msg.AmountOut)
if not ok then
msg.reply({Error = err})
return
end
amountOut = msg.AmountOut
end
-- 90 seconds
local expireDate = msg.Timestamp + FFP.TimeoutPeriod
local res = Send({
Target = FFP.Settle,
Action = 'CreateNote',
AssetID = msg.TokenOut,
Amount = tostring(amountOut),
HolderAssetID = msg.TokenIn,
HolderAmount = msg.AmountIn,
IssueDate = tostring(msg.Timestamp),
ExpireDate = tostring(expireDate),
Version = FFP.SettleVersion
}).receive()
local noteID = res.NoteID
local note = json.decode(res.Data)
note.MakeTx = msg.Id
FFP.Notes[noteID] = note
FFP.MakeTxToNoteID[msg.Id] = noteID
-- remove expired notes in Notes
for noteID, note in pairs(FFP.Notes) do
if note.Status == 'Open' and note.ExpireDate and note.ExpireDate < msg.Timestamp then
FFP.Notes[noteID] = nil
FFP.MakeTxToNoteID[note.MakeTx] = nil
end
end
msg.reply({
Action = 'OrderMade-Notice',
NoteID = noteID,
Data = json.encode(note)
})
end
)
代码逻辑:
校验输入参数和当前池子状态,池子的 Executing 相当于全局锁,确保池子按照先后顺序结算 Note。
调用 pool.getAmountOut 计算交易的输出。
调用 FFP 协议创建票据,并存储到 FFP.Notes。
票据结算执行
code
Handlers.add('ffp.execute', 'Execute',
function(msg)
assert(msg.From == FFP.Settle, 'Only settle can start exectue')
assert(type(msg.NoteID) == 'string', 'NoteID is required')
assert(type(msg.SettleID) == 'string', 'SettleID is required')
if FFP.Executing then
msg.reply({Action= 'Reject', Error = 'err_executing', SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
local note = FFP.Notes[msg.NoteID]
if not note then
msg.reply({Action= 'Reject', Error = 'err_not_found', SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
if note.Status ~= 'Open' then
msg.reply({Action= 'Reject', Error = 'err_not_open', SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
if note.Issuer ~= ao.id then
msg.reply({Action = 'Reject', Error = 'err_invalid_issuer', SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
if note.ExpireDate and note.ExpireDate < msg.Timestamp then
FFP.Notes[note.NoteID] = nil
FFP.MakeTxToNoteID[note.MakeTx] = nil
msg.reply({Action= 'Reject', Error = 'err_expired', SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
local pool = getPool(note.HolderAssetID, note.AssetID)
local ok, err = validateAmountOut(pool, note.HolderAssetID, note.HolderAmount, note.AssetID, note.Amount)
if not ok then
msg.reply({Action= 'Reject', Error = err, SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
msg.reply({Action = 'ExecuteStarted', SettleID = msg.SettleID, NoteID = msg.NoteID})
FFP.Executing = true
note.Status = 'Executing'
Send({Target = note.AssetID, Action = 'Transfer', Quantity = note.Amount, Recipient = FFP.Settle,
['X-FFP-SettleID'] = msg.SettleID,
['X-FFP-NoteID'] = msg.NoteID,
['X-FFP-For'] = 'Execute'
})
end
)
FFP 结算中心会主动调用该接口进行票据结算操作。
代码逻辑:
验证票据和池子状态。
调用 validateAmountOut 验证当前结算的票据是否满足 amm 做市价格。
转移票据上指定的资产到 FFP 结算中心进行结算操作。
结算完成通知
code
Handlers.add('ffp.done',
function(msg) return (msg.Action == 'Credit-Notice') and (msg['X-FFP-For'] == 'Settled' or msg['X-FFP-For'] == 'Refund') end,
function(msg)
assert(msg.Sender == FFP.Settle, 'Only settle can send settled or refund notice')
local noteID = msg['X-FFP-NoteID']
local note = FFP.Notes[noteID]
if not note then
print('no note found when settled: ' .. noteID)
return
end
local pool = getPool(note.HolderAssetID, note.AssetID)
if msg['X-FFP-For'] == 'Settled' then
pool:updateAfterSwap(note.HolderAssetID, note.HolderAmount, note.AssetID, note.Amount)
end
FFP.Notes[noteID].Status = msg['X-FFP-For']
FFP.Executing = false
FFP.Notes[noteID] = json.decode(Send({Target = FFP.Settle, Action = 'GetNote', NoteID = noteID}).receive().Data)
end
)
该方法用于监听 FFP 结算 Note 转移过来的资产或者资产的退款(结算失败)。
代码逻辑:
判断 Note 是否存在。
判断 Note 相关的 pool 是否存在
如果是结算成功,则需要修改 pool 中的做市流动性
更新相关变量。
3. 查询接口
查询 pools
code
Handlers.add('ffp.pools', 'FFP.GetPools',
function(msg)
local pools = {}
for poolId, pool in pairs(FFP.Pools) do
pools[poolId] = pool:info()
end
msg.reply({Data = json.encode(pools)})
end
)
该方法返回创建的所有 pool 信息。
查询 Note
code
Handlers.add('ffp.getNote', 'FFP.GetNote',
function(msg)
assert(type(msg.MakeTx) == 'string', 'MakeTx is required')
local note = FFP.Notes[FFP.MakeTxToNoteID[msg.MakeTx]] or ''
msg.reply({NoteID=note.NoteID, Data = json.encode(note)})
end
)
该方法根据 MakeTx 查询对应的 Note 并返回。
查询价格
code
Handlers.add('ffp.getAmountOut', 'FFP.GetAmountOut',
function(msg)
if FFP.Executing then
msg.reply({Error = 'err_executing'})
return
end
local ok, err = validateOrderMsg(msg)
if not ok then
msg.reply({Error = err})
return
end
local pool = getPool(msg.TokenIn, msg.TokenOut)
local tokenOut, amountOut = pool:getAmountOut(msg.TokenIn, msg.AmountIn)
if not amountOut or amountOut == '0' then
msg.reply({Error = 'err_no_amount_out'})
return
end
msg.reply({
TokenIn = msg.TokenIn,
AmountIn = msg.AmountIn,
TokenOut = tokenOut,
AmountOut = tostring(amountOut)
})
end
)
该方法用于返回实时兑换价格信息。
完整代码
amm.lua
local json = require('json')
local bint = require('.bint')(512)
local utils = require('.utils')
local poolUniswapV2 = require('.algo.uniswapv2')
local poolBigOrder = require('.algo.bigorder')
FFP = FFP or {}
-- config
FFP.Settle = FFP.Settle or 'rKpOUxssKxgfXQOpaCq22npHno6oRw66L3kZeoo_Ndk'
FFP.Publisher = FFP.Publisher or '8VzqSX_0rdAr99P3QIYd0bRG-XySTQUNFlfJf20zNEo'
FFP.SettleVersion = FFP.SettleVersion or '0.31'
FFP.MaxNotesToSettle = FFP.MaxNotesToSettle or 2
FFP.TimeoutPeriod = FFP.TimeoutPeriod or 90000
-- database or state
FFP.Pools = FFP.Pools or {}
FFP.Notes = FFP.Notes or {}
FFP.MakeTxToNoteID = FFP.MakeTxToNoteID or {}
FFP.Executing = FFP.Executing or false
-- amm pool functions
FFP.AlgoToPool = FFP.AlgoToPool or {
UniswapV2 = poolUniswapV2,
BigOrder = poolBigOrder
}
local function getPoolId(X, Y)
if X > Y then
return Y .. ':' .. X
end
return X .. ':' .. Y
end
local function getPool(X, Y)
return FFP.Pools[getPoolId(X, Y)]
end
local function validateAmount(amount)
if not amount then
return false, 'err_no_amount'
end
local ok, qty = pcall(bint, amount)
if not ok then
return false, 'err_invalid_amount'
end
if not bint.__lt(0, qty) then
return false, 'err_negative_amount'
end
return true, nil
end
-- tokenIn, tokenOut, amountIn, amountOut(optional)
local function validateOrderMsg(msg)
local Pool = getPool(msg.TokenIn, msg.TokenOut)
if not Pool then
return false, 'err_pool_not_found'
end
local ok, err = validateAmount(msg.AmountIn)
if not ok then
return false, 'err_invalid_amount_in'
end
if msg.AmountOut then
local ok, err = validateAmount(msg.AmountOut)
if not ok then
return false, 'err_invalid_amount_out'
end
end
return true, nil
end
local function validateAmountOut(pool, tokenIn, amountIn, tokenOut, expectedAmountOut)
local tokenOut_, amountOut = pool:getAmountOut(tokenIn, amountIn)
if not amountOut or amountOut == '0' then
return false, 'err_no_amount_out'
end
if tokenOut_ ~= tokenOut then
return false, 'err_invalid_token_out'
end
if expectedAmountOut then
if utils.lt(amountOut, expectedAmountOut) then
return false, 'err_amount_out_too_small'
end
end
return true, nil
end
Handlers.add('ffp.withdraw', 'FFP.Withdraw',
function(msg)
assert(msg.From == Owner or msg.From == ao.id, 'Only owner can withdraw')
assert(type(msg.Token) == 'string', 'Token is required')
assert(type(msg.Amount) == 'string', 'Amount is required')
assert(bint.__lt(0, bint(msg.Amount)), 'Amount must be greater than 0')
Send({
Target = msg.Token,
Action = 'Transfer',
Quantity = msg.Amount,
Recipient = Owner
})
end
)
Handlers.add('ffp.pools', 'FFP.GetPools',
function(msg)
local pools = {}
for poolId, pool in pairs(FFP.Pools) do
pools[poolId] = pool:info()
end
msg.reply({Data = json.encode(pools)})
end
)
Handlers.add('ffp.addPool', 'FFP.AddPool',
function(msg)
assert(msg.From == Owner or msg.From == ao.id, 'Only owner can add pool')
assert(type(msg.X) == 'string', 'X is required')
assert(type(msg.Y) == 'string', 'Y is required')
assert(msg.X < msg.Y, 'X must be less than Y')
assert(type(msg.Algo) == 'string', 'Algo is required')
assert(type(msg.Data) == 'string', 'Params is required')
local poolId = getPoolId(msg.X, msg.Y)
if FFP.Pools[poolId] then
msg.reply({Error = 'Pool already exists'})
return
end
local P = FFP.AlgoToPool[msg.Algo]
if not P then
msg.reply({Error = 'Unsupported amm algo'})
return
end
local pool = P.new(msg.X, msg.Y, json.decode(msg.Data))
if not pool then
msg.reply({Error = 'Invalid pool params'})
return
end
FFP.Pools[poolId] = pool
msg.reply({Action = 'PoolAdded-Notice', Pool = poolId})
Send({Target = FFP.Publisher, Action = 'PoolAdded-Notice', Creator = ao.id, Data = json.encode(pool)})
end
)
Handlers.add('ffp.updateLiquidity', 'FFP.UpdateLiquidity',
function(msg)
assert(msg.From == Owner or msg.From == ao.id, 'Only owner can update liquidity')
assert(type(msg.Y) == 'string', 'Y is required')
assert(msg.X < msg.Y, 'X must be less than Y')
assert(type(msg.Data) == 'string', 'Params is required')
local pool = getPool(msg.X, msg.Y)
if not pool then
msg.reply({Error = 'Pool not found'})
return
end
local ok = pool:updateLiquidity(json.decode(msg.Data))
if not ok then
msg.reply({Error = 'Invalid updateLiquidity params'})
return
end
msg.reply({Action = 'LiquidityUpdated-Notice', Data = json.encode(pool)})
end
)
Handlers.add('ffp.getAmountOut', 'FFP.GetAmountOut',
function(msg)
if FFP.Executing then
msg.reply({Error = 'err_executing'})
return
end
local ok, err = validateOrderMsg(msg)
if not ok then
msg.reply({Error = err})
return
end
local pool = getPool(msg.TokenIn, msg.TokenOut)
local tokenOut, amountOut = pool:getAmountOut(msg.TokenIn, msg.AmountIn)
if not amountOut or amountOut == '0' then
msg.reply({Error = 'err_no_amount_out'})
return
end
msg.reply({
TokenIn = msg.TokenIn,
AmountIn = msg.AmountIn,
TokenOut = tokenOut,
AmountOut = tostring(amountOut)
})
end
)
Handlers.add('ffp.makeOrder', 'FFP.MakeOrder',
function(msg)
if FFP.Executing then
msg.reply({Error = 'err_executing'})
return
end
local ok, err = validateOrderMsg(msg)
if not ok then
msg.reply({Error = err})
return
end
local pool = getPool(msg.TokenIn, msg.TokenOut)
local tokenOut, amountOut = pool:getAmountOut(msg.TokenIn, msg.AmountIn)
if not amountOut or amountOut == '0' then
msg.reply({Error = 'err_no_amount_out'})
return
end
if msg.AmountOut then
local ok, err = validateAmountOut(pool, msg.TokenIn, msg.AmountIn, msg.TokenOut, msg.AmountOut)
if not ok then
msg.reply({Error = err})
return
end
amountOut = msg.AmountOut
end
-- 90 seconds
local expireDate = msg.Timestamp + FFP.TimeoutPeriod
local res = Send({
Target = FFP.Settle,
Action = 'CreateNote',
AssetID = msg.TokenOut,
Amount = tostring(amountOut),
HolderAssetID = msg.TokenIn,
HolderAmount = msg.AmountIn,
IssueDate = tostring(msg.Timestamp),
ExpireDate = tostring(expireDate),
Version = FFP.SettleVersion
}).receive()
local noteID = res.NoteID
local note = json.decode(res.Data)
note.MakeTx = msg.Id
FFP.Notes[noteID] = note
FFP.MakeTxToNoteID[msg.Id] = noteID
-- remove expired notes in Notes
for noteID, note in pairs(FFP.Notes) do
if note.Status == 'Open' and note.ExpireDate and note.ExpireDate < msg.Timestamp then
FFP.Notes[noteID] = nil
FFP.MakeTxToNoteID[note.MakeTx] = nil
end
end
msg.reply({
Action = 'OrderMade-Notice',
NoteID = noteID,
Data = json.encode(note)
})
end
)
Handlers.add('ffp.getNote', 'FFP.GetNote',
function(msg)
assert(type(msg.MakeTx) == 'string', 'MakeTx is required')
local note = FFP.Notes[FFP.MakeTxToNoteID[msg.MakeTx]] or ''
msg.reply({NoteID=note.NoteID, Data = json.encode(note)})
end
)
Handlers.add('ffp.execute', 'Execute',
function(msg)
assert(msg.From == FFP.Settle, 'Only settle can start exectue')
assert(type(msg.NoteID) == 'string', 'NoteID is required')
assert(type(msg.SettleID) == 'string', 'SettleID is required')
if FFP.Executing then
msg.reply({Action= 'Reject', Error = 'err_executing', SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
local note = FFP.Notes[msg.NoteID]
if not note then
msg.reply({Action= 'Reject', Error = 'err_not_found', SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
if note.Status ~= 'Open' then
msg.reply({Action= 'Reject', Error = 'err_not_open', SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
if note.Issuer ~= ao.id then
msg.reply({Action = 'Reject', Error = 'err_invalid_issuer', SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
if note.ExpireDate and note.ExpireDate < msg.Timestamp then
FFP.Notes[note.NoteID] = nil
FFP.MakeTxToNoteID[note.MakeTx] = nil
msg.reply({Action= 'Reject', Error = 'err_expired', SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
local pool = getPool(note.HolderAssetID, note.AssetID)
local ok, err = validateAmountOut(pool, note.HolderAssetID, note.HolderAmount, note.AssetID, note.Amount)
if not ok then
msg.reply({Action= 'Reject', Error = err, SettleID = msg.SettleID, NoteID = msg.NoteID})
return
end
msg.reply({Action = 'ExecuteStarted', SettleID = msg.SettleID, NoteID = msg.NoteID})
FFP.Executing = true
note.Status = 'Executing'
Send({Target = note.AssetID, Action = 'Transfer', Quantity = note.Amount, Recipient = FFP.Settle,
['X-FFP-SettleID'] = msg.SettleID,
['X-FFP-NoteID'] = msg.NoteID,
['X-FFP-For'] = 'Execute'
})
end
)
Handlers.add('ffp.done',
function(msg) return (msg.Action == 'Credit-Notice') and (msg['X-FFP-For'] == 'Settled' or msg['X-FFP-For'] == 'Refund') end,
function(msg)
assert(msg.Sender == FFP.Settle, 'Only settle can send settled or refund notice')
local noteID = msg['X-FFP-NoteID']
local note = FFP.Notes[noteID]
if not note then
print('no note found when settled: ' .. noteID)
return
end
local pool = getPool(note.HolderAssetID, note.AssetID)
if msg['X-FFP-For'] == 'Settled' then
pool:updateAfterSwap(note.HolderAssetID, note.HolderAmount, note.AssetID, note.Amount)
end
FFP.Notes[noteID].Status = msg['X-FFP-For']
FFP.Executing = false
FFP.Notes[noteID] = json.decode(Send({Target = FFP.Settle, Action = 'GetNote', NoteID = noteID}).receive().Data)
end
)
utils.lua
local json = require('json')
local bint = require('.bint')(512)
local mod = {
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,
lt = function (a, b)
return bint.__lt(bint(a), bint(b))
end
}
mod.getNoteIDs = function(data)
if string.find(data, "null") then
return nil
end
local noteIDs, err = json.decode(data)
if err then
return nil
end
if type(noteIDs) == "string" then
return {noteIDs}
end
if type(noteIDs) == "table" then
for i = 1, #noteIDs do
if noteIDs[i] == nil or type(noteIDs[i]) ~= "string" then
return nil
end
end
return noteIDs
end
return nil
end
mod.SettlerIO = function (notes)
local settlerIn = {}
local settlerOut = {}
local bc = {}
for _, note in ipairs(notes) do
if not bc[note.AssetID] then bc[note.AssetID] = bint(0) end
if not bc[note.HolderAssetID] then bc[note.HolderAssetID] = bint(0) end
bc[note.AssetID] = mod.add(bc[note.AssetID], note.Amount)
bc[note.HolderAssetID] = mod.subtract(bc[note.HolderAssetID], note.HolderAmount)
end
for k, v in pairs(bc) do
if v == '0' then bc[k] = nil end
end
for k, v in pairs(bc) do
if mod.lt(v, '0') then
settlerOut[k] = v
else
settlerIn[k] = v
end
end
return settlerIn, settlerOut
end
return mod
./algo/bigorder.lua
local bint = require('.bint')(1024)
local Pool = {}
Pool.__index = Pool
local utils = {
add = function (a,b)
return tostring(bint(a) + bint(b))
end,
subtract = function (a,b)
return tostring(bint(a) - bint(b))
end,
mul = function(a, b)
return tostring(bint.__mul(bint(a), bint(b)))
end,
div = function(a, b)
return tostring(bint.udiv(bint(a), bint(b)))
end,
lt = function (a, b)
return bint.__lt(bint(a), bint(b))
end
}
local function validateAmount(amount)
if not amount then
return false, 'err_no_amount'
end
local ok, qty = pcall(bint, amount)
if not ok then
return false, 'err_invalid_amount'
end
if not utils.lt(0, qty) then
return false, 'err_negative_amount'
end
return true, nil
end
local function validateInitParams(params)
if not params then
return false
end
return params.tokenOut and params.tokenIn and validateAmount(params.amountOut) and validateAmount(params.amountIn)
end
function Pool.new(x, y, params)
if not validateInitParams(params) then
return nil
end
local self = setmetatable({}, Pool)
self.algo = 'BigOrder'
self.x = x
self.y = y
self.tokenIn = params.tokenIn
self.tokenOut = params.tokenOut
self.amountOut = params.amountOut
self.amountIn = params.amountIn
self.balanceTokenIn = '0'
self.balanceTokenOut = self.amountOut
return self
end
function Pool:balances()
local balance = {
[self.tokenOut] = self.balanceTokenOut,
[self.tokenIn] = self.balanceTokenIn
}
return balance
end
function Pool:info()
local info = {
x = self.x,
y = self.y,
algo = self.algo,
tokenIn = self.tokenIn,
tokenOut = self.tokenOut,
amountOut = self.amountOut,
amountIn = self.amountIn,
balances = self:balances()
}
return info
end
function Pool:updateLiquidity(params)
return false
end
function Pool:updateAfterSwap(tokenIn, amountIn, tokenOut, amountOut)
self.balanceTokenIn = utils.add(self.balanceTokenIn, amountIn)
self.balanceTokenOut = utils.subtract(self.balanceTokenOut, amountOut)
end
function Pool:getAmountOut(tokenIn, amountIn)
if tokenIn == self.tokenIn then
local amountOut = utils.div(utils.mul(amountIn, self.amountOut), self.amountIn)
if utils.lt(amountOut, self.balanceTokenOut) then
return self.tokenOut, tostring(amountOut)
end
end
return nil, nil
end
return Pool
./algo/uniswapv2.lua
local bint = require('.bint')(1024)
local Pool = {}
Pool.__index = Pool
local utils = {
add = function (a,b)
return tostring(bint(a) + bint(b))
end,
subtract = function (a,b)
return tostring(bint(a) - bint(b))
end,
mul = function(a, b)
return tostring(bint.__mul(bint(a), bint(b)))
end,
div = function(a, b)
return tostring(bint.udiv(bint(a), bint(b)))
end,
lt = function (a, b)
return bint.__lt(bint(a), bint(b))
end
}
local function validateAmount(amount)
if not amount then
return false, 'err_no_amount'
end
local ok, qty = pcall(bint, amount)
if not ok then
return false, 'err_invalid_amount'
end
if not utils.lt(0, qty) then
return false, 'err_negative_amount'
end
return true, nil
end
local function getAmountOut(amountIn, reserveIn, reserveOut, fee)
local amountInWithFee = utils.mul(amountIn, utils.subtract(10000, fee))
local numerator = utils.mul(amountInWithFee, reserveOut)
local denominator = utils.add(utils.mul(10000, reserveIn), amountInWithFee)
return utils.div(numerator, denominator)
end
local function validateInitParams(params)
if not params then
return false
end
return validateAmount(params.fee) and validateAmount(params.px) and validateAmount(params.py)
end
local function validateUpdateLiquidityParams(params)
if not params then
return false
end
return validateAmount(params.px) and validateAmount(params.py)
end
function Pool.new(x, y, params)
if not validateInitParams(params) then
return nil
end
local self = setmetatable({}, Pool)
self.algo = 'UniswapV2'
self.x = x
self.y = y
self.fee = params.fee
self.px = params.px
self.py = params.py
return self
end
function Pool:balances()
local balance = {
[self.x] = self.px,
[self.y] = self.py
}
return balance
end
function Pool:info()
local info = {
x = self.x,
y = self.y,
fee = self.fee,
algo = self.algo,
px = self.px,
py = self.py,
balances = self:balances()
}
return info
end
function Pool:updateLiquidity(params)
if not validateUpdateLiquidityParams(params) then
return false
end
self.px = params.px
self.py = params.py
return true
end
function Pool:updateAfterSwap(tokenIn, amountIn, tokenOut, amountOut)
if tokenIn == self.x then
self.px = utils.add(self.px, amountIn)
self.py = utils.subtract(self.py, amountOut)
else
self.py = utils.add(self.py, amountIn)
self.px = utils.subtract(self.px, amountOut)
end
end
function Pool:getAmountOut(tokenIn, amountIn)
local tokenOut = self.y
local reserveIn = bint(self.px)
local reserveOut = bint(self.py)
if tokenIn == self.y then
reserveIn, reserveOut = reserveOut, reserveIn
tokenOut = self.x
end
local amountOut = getAmountOut(bint(amountIn), reserveIn, reserveOut, bint(self.fee))
return tokenOut, tostring(amountOut)
end
return Pool
开发之前,把以上代码按照路径复制到开发环境即可。
总结
AMM 蓝图通过灵活的架构设计,允许开发者轻松实现和扩展各种 AMM 功能,同时保证交易和流动性管理的高效性。
Last updated
Was this helpful?