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

feat: add r/demo/foo20_lp #931

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/gno.land/r/demo/foo20/foo20.gno
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ var (

func init() {
foo = grc20.NewAdminToken("Foo", "FOO", 4)
foo.Mint(admin, 1000000*10000) // @administrator (1M)
foo.Mint("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", 10000*10000) // @manfred (10k)
foo.Mint(admin, 1_000_000*10_000) // @administrator (1M)
foo.Mint("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", 10_000*10_000) // @manfred (10k)
}

// method proxies as public functions.
Expand Down
45 changes: 45 additions & 0 deletions examples/gno.land/r/demo/foo20_lp/doc.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
Package foo20_lp provides a simple implementation of a fixed price
liquidity queue for swapping two types of tokens.

A liquidity queue represents a queue of tokens that can be swapped for tokens of
a different type at a fixed price. When a token is swapped, it is removed
from its queue and a token of the other type is removed from the other queue.
If there are not enough tokens in the other queue to swap with, the token is
added to its queue to be swapped later when more tokens are added to the
other queue.

ASK
+------------+ +------------+ +------------+
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | +------------+ | |
| | | #2 | | |
| | +------------+ | |
| | | #1 | | |
+------------+ +------------+ +------------+
| | | | | #3 |
| | | | +------------+
| | | | | #4 |
| | | | +------------+
| | | | | |
| | | | | |
| | | | | |
| | | | | |
+------------+ +------------+ +------------+
BID

The provided diagram showcases two queues: "ASK" and "BID". In this system, only
one queue can be active at a time. If the "ASK" queue has orders, the system
is selling, and new buy orders ("BID") are queued for later. If the "BID"
queue is active, the system is buying, and new sell orders ("ASK") are
queued. This mechanism ensures first-in-first-out (FIFO) order handling at a
fixed price.

Please note that this is a simple implementation and does not handle many
practical considerations that would need to be addressed in a real-world
application.
*/
package foo20_lp // import "gno.land/r/demo/foo20_lp"
8 changes: 8 additions & 0 deletions examples/gno.land/r/demo/foo20_lp/entry.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package foo20_lp

import "std"

type Entry struct {
Addr std.Address
Amount int64
}
30 changes: 30 additions & 0 deletions examples/gno.land/r/demo/foo20_lp/fifo.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package foo20_lp

import "std"

type Fifo []Entry

func (f Fifo) AddrHasEntries(addr std.Address) bool {
for _, entry := range f {
if entry.Addr == addr {
return true
}
}
return false
}

func (f Fifo) Empty() bool {
return len(f) == 0
}

func (f Fifo) Size() int {
return len(f)
}

func (f Fifo) TotalAmount() int64 {
var total int64
for _, entry := range f {
total += entry.Amount
}
return total
}
148 changes: 148 additions & 0 deletions examples/gno.land/r/demo/foo20_lp/lp.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package foo20_lp

import (
"std"

"gno.land/p/demo/ufmt"
)

// TODO: replace int64 with bigint
// TODO: is it a good contract to show how a contract can take some usage fees? probably.
// TODO: extract the generic & pure part of this contract to a p/ package.
// TODO: add stats?

const (
foo20PriceInUgnot int64 = 100 // ugnot has 6 digits, foo20 has 4 digits
foo20MinDeposit int64 = 2 // min 1
ugnotMinDeposit int64 = 300 // min foo20PriceInUgnot
)

var (
foo20Queue Fifo
ugnotQueue Fifo
)

func DepositFoo20(amount int64) {
if amount < foo20MinDeposit {
panic(ufmt.Sprintf("min deposit: %d foo20.", foo20MinDeposit))
}
// XXX: if foo20PriceInUgnot was < 1, we should check for `amount % ugnotPriceInFoo20 == 0`.

caller := std.GetOrigCaller()
pkgaddr := std.GetOrigPkgAddr()

// TODO: store amounts on contracts' address or work with approve maybe?

rest := amount
for _, entry := range ugnotQueue {
if rest == 0 {
break
}

// full swap
if rest > entry.Amount {
println("not implemented: A")
// TODO: send foo20 to entry.Addr
// TODO: delete entry from queue
rest -= entry.Amount // update rest counter
continue
}

// partial swap
{
println("not implemented: B")
// partial swap
// TODO: send `rest` foo20 to entry.Addr
// TODO: send `rest` ugnot to caller
entry.Amount -= rest // patch entry
rest = 0
}
}

// send swapped ugnots to caller
banker := std.GetBanker(std.BankerTypeOrigSend)
swappedFoo20 := amount - rest
returnedUgnot := swappedFoo20 * foo20PriceInUgnot
send := std.Coins{{"ugnot", returnedUgnot}}
banker.SendCoins(caller, pkgaddr, send)

// if we have rest, add a new entry in the queue
if rest > 0 {
entry := Entry{Addr: caller, Amount: rest}
foo20Queue = append(foo20Queue, entry)
}
}

func DepositUgnot() {
caller := std.GetOrigCaller()
pkgaddr := std.GetOrigPkgAddr()
sent := std.GetOrigSend()
amount := sent.AmountOf("ugnot")

println("@@@@@@@@@@@@@", caller, pkgaddr, sent, amount)

if amount < ugnotMinDeposit {
panic(ufmt.Sprintf("jmin deposit: %d ugnot.", ugnotMinDeposit))
}
if amount%foo20PriceInUgnot != 0 {
panic(ufmt.Sprintf("ugnot deposit should be a multiple of %d, was %d.", foo20PriceInUgnot, amount))
}
println("not implemented: C")
}

func WithdrawFoo20(amount int64) {
println("not implemented: D")
}

func WithdrawUgnot(amount int64) {
println("not implemented: E")
}

func Render(path string) string {
switch path {
case "": // home
var (
ugnotEntries = ugnotQueue.Size()
ugnotTotal = ugnotQueue.TotalAmount()
foo20Entries = foo20Queue.Size()
foo20Total = foo20Queue.TotalAmount()
)
switch {
case ugnotEntries == 0 && foo20Entries == 0:
return "LP is empty."
case ugnotEntries > 0:
foo20Capa := ugnotTotal / foo20PriceInUgnot
return ufmt.Sprintf("LP has a total of %d ugnot in %d orders for a capacity of %d foo20.", ugnotTotal, ugnotEntries, foo20Capa)
case foo20Entries > 0:
ugnotCapa := foo20Total * foo20PriceInUgnot
return ufmt.Sprintf("LP has a total of %d foo20 in %d orders for a capacity of %d ugnot.", foo20Total, foo20Entries, ugnotCapa)
default:
panic("should not happen")
}
case "queues":
if foo20Queue.Empty() && ugnotQueue.Empty() {
return "## Queues are empty"
}

output := ""
if !foo20Queue.Empty() {
output += ufmt.Sprintf("## foo20Queue (total=%d, orders=%d)\n", foo20Queue.TotalAmount(), foo20Queue.Size())
var cumulated int64
for _, entry := range foo20Queue {
cumulated += entry.Amount
output += ufmt.Sprintf("- addr=%s amount=%d (cum=%d)\n", entry.Addr, entry.Amount, cumulated)
}
}
if !ugnotQueue.Empty() {
output += ufmt.Sprintf("## ugnotQueue (total=%d, orders=%d)\n", ugnotQueue.TotalAmount(), ugnotQueue.Size())
var cumulated int64
for _, entry := range ugnotQueue {
cumulated += entry.Amount
output += ufmt.Sprintf("- addr=%s amount=%d (cum=%d)\n", entry.Addr, entry.Amount, cumulated)
}
}
return output
default:
return "404"
}
}
7 changes: 7 additions & 0 deletions examples/gno.land/r/demo/foo20_lp/lp_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package foo20_lp

import "testing"

func TestPackage(t *testing.T) {
println("OK")
}
118 changes: 118 additions & 0 deletions examples/gno.land/r/demo/foo20_lp/z0_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// PKGPATH: gno.land/r/demo/foo20_lp_test
package foo20_lp_test

import (
"fmt"
"std"

"gno.land/p/demo/testutils"
"gno.land/r/demo/foo20"
"gno.land/r/demo/foo20_lp"
"gno.land/r/demo/users"
)

var (
addr1 = testutils.TestAddress("test1")
addr2 = testutils.TestAddress("test2")
addr3 = testutils.TestAddress("test3")
addrc = std.DerivePkgAddr("gno.land/r/demo/foo20_lp")
addrt = std.DerivePkgAddr("gno.land/r/demo/foo20_lp_test")
)

func main() {
std.TestSetOrigPkgAddr(addrc)
std.TestIssueCoins(addrc, std.Coins{{"ugnot", 100001337}}) // TODO: remove this

// issue ugnots
std.TestIssueCoins(addr1, std.Coins{{"ugnot", 100000001}})
std.TestIssueCoins(addr2, std.Coins{{"ugnot", 100000002}})
std.TestIssueCoins(addr3, std.Coins{{"ugnot", 100000003}})

// issue foo20
foo20Admin := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj")
std.TestSetOrigCaller(foo20Admin)
foo20.Transfer(users.AddressOrName(addr1), 10001)
foo20.Transfer(users.AddressOrName(addr2), 10002)
foo20.Transfer(users.AddressOrName(addr3), 10003)

// print initial state
// printBalances()
// println(foo20_lp.Render("queues"))
// println("A -", foo20_lp.Render(""))

// addr1 deposits ugnots
std.TestSetOrigCaller(addr1)
std.TestSetOrigSend(std.Coins{{"ugnot", 123_400}}, nil)
foo20_lp.DepositUgnot()
println("B -", foo20_lp.Render(""))

// addr1 deposits ugnots again
std.TestSetOrigCaller(addr1)
std.TestSetOrigSend(std.Coins{{"ugnot", 567_800}}, nil)
foo20_lp.DepositUgnot()
println("C -", foo20_lp.Render(""))

println(foo20_lp.Render("queues"))
printBalances()
return

foo20_lp.DepositFoo20(10_001)
foo20_lp.DepositFoo20(10_002)
println("C -", foo20_lp.Render(""))
foo20_lp.DepositFoo20(10_003)
foo20_lp.DepositFoo20(10_004)
foo20_lp.DepositFoo20(10_005)
foo20_lp.DepositFoo20(10_006)
println(foo20_lp.Render("queues"))
println("D -", foo20_lp.Render(""))
println("E -", foo20_lp.Render(""))
foo20_lp.WithdrawFoo20(101)
println("F -", foo20_lp.Render(""))
foo20_lp.WithdrawFoo20(102)
println("G -", foo20_lp.Render(""))
foo20_lp.WithdrawUgnot(103)
println("H -", foo20_lp.Render(""))
println(foo20_lp.Render("queues"))
println("--------------------------")
printBalances()
}

func printBalances() {
printSingleBalance := func(name string, addr std.Address) {
foo20Bal := foo20.BalanceOf(users.AddressOrName(addr))
std.TestSetOrigCaller(addr)
abanker := std.GetBanker(std.BankerTypeOrigSend)
acoins := abanker.GetCoins(addr).AmountOf("ugnot")
bbanker := std.GetBanker(std.BankerTypeRealmIssue)
bcoins := bbanker.GetCoins(addr).AmountOf("ugnot")
cbanker := std.GetBanker(std.BankerTypeRealmSend)
ccoins := cbanker.GetCoins(addr).AmountOf("ugnot")
dbanker := std.GetBanker(std.BankerTypeReadonly)
dcoins := dbanker.GetCoins(addr).AmountOf("ugnot")
fmt.Printf("| %-13s | addr=%s | foo20=%-5d | ugnot=%-9d %-9d %-9d %-9d |\n",
name, addr, foo20Bal, acoins, bcoins, ccoins, dcoins)
}
println("-----------")
printSingleBalance("foo20_lp_test", addrt)
printSingleBalance("foo20_lp", addrc)
printSingleBalance("addr1", addr1)
printSingleBalance("addr2", addr2)
printSingleBalance("addr3", addr3)
println("-----------")
}

// Output:
// @@@@@@@@@@@@@ g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 g12ddhtqgr5qsgy2keefnehrlwtwj09nner5jt3u 123400ugnot 123400
// not implemented: C
// B - LP is empty.
// @@@@@@@@@@@@@ g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 g12ddhtqgr5qsgy2keefnehrlwtwj09nner5jt3u 567800ugnot 567800
// not implemented: C
// C - LP is empty.
// ## Queues are empty
// -----------
// | foo20_lp_test | addr=g1erg29nwj2yu4ahl585369p2pxrfgdjjtha8zwm | foo20=0 | ugnot=200000000 200000000 200000000 200000000 |
// | foo20_lp | addr=g12ddhtqgr5qsgy2keefnehrlwtwj09nner5jt3u | foo20=0 | ugnot=100001337 100001337 100001337 100001337 |
// | addr1 | addr=g1w3jhxap3ta047h6lta047h6lta047h6l4mfnm7 | foo20=10001 | ugnot=100000001 100000001 100000001 100000001 |
// | addr2 | addr=g1w3jhxapjta047h6lta047h6lta047h6laqcyu4 | foo20=10002 | ugnot=100000002 100000002 100000002 100000002 |
// | addr3 | addr=g1w3jhxapnta047h6lta047h6lta047h6lzfhfxt | foo20=10003 | ugnot=100000003 100000003 100000003 100000003 |
// -----------
Loading