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/atomicswap #2510

Merged
merged 107 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
af8f629
feat: add r/demo/atomicswap
moul Jul 5, 2024
9b7190e
chore: fixup
moul Jul 5, 2024
1b0a95f
chore: fixup
moul Jul 5, 2024
6b89115
Merge branch 'master' into dev/moul/atomicswap
moul Jul 5, 2024
b8a8282
override pv and rlm if method receiver has one
jaekwon Jul 5, 2024
23f7173
add tests
jaekwon Jul 5, 2024
b0397ed
Merge branch 'master' into dev/jae/boundmethodrealm
jaekwon Jul 5, 2024
b1254a7
chore: add missing gno.mod files
moul Jul 5, 2024
59443ee
chore: fixup
moul Jul 6, 2024
d317eaa
chore: fixup
moul Jul 6, 2024
6a41a93
resave after init
jaekwon Jul 6, 2024
3ab2e5a
chore: fixup
moul Jul 6, 2024
d23ab40
Merge remote-tracking branch 'origin/dev/jae/boundmethodrealm' into d…
moul Jul 6, 2024
08d0c11
Merge branch 'master' into dev/moul/atomicswap
moul Jul 6, 2024
ade3d05
Merge branch 'master' into dev/moul/atomicswap
moul Jul 6, 2024
437df89
chore: fixup
moul Jul 6, 2024
b45a63d
chore(examples): make lint -> verbose
moul Jul 6, 2024
ae76c6e
chore: fixup
moul Jul 6, 2024
552f06b
chore: fixup
moul Jul 6, 2024
25e3d6e
chore: fixup
moul Jul 6, 2024
82219a4
Merge branch 'master' into dev/moul/atomicswap
moul Jul 6, 2024
aa51902
chore: fixup
moul Jul 6, 2024
be4615c
feat(examples): refactor grc20
moul Jul 6, 2024
9ec3353
chore: fixup
moul Jul 6, 2024
d1af9ce
chore: fixup
moul Jul 6, 2024
360c0c8
chore: fixup
moul Jul 6, 2024
34b35b9
chore: fixup
moul Jul 7, 2024
a854c26
chore: fixup
moul Jul 7, 2024
27e979f
chore: fixup
moul Jul 7, 2024
d8438d2
chore: fixup
moul Jul 7, 2024
5d97b41
feat: add embedmd in make -C docs/ build
moul Jul 7, 2024
d985d4f
chore: update docs
moul Jul 7, 2024
9eeb026
feat: add embedmd in make -C docs/ build
moul Jul 7, 2024
2a3c3ac
chore: regenerate docs
moul Jul 7, 2024
2d83f2b
chore(ci): check docs' build
moul Jul 7, 2024
42b2061
Merge branch 'dev/moul/docs-build' into dev/moul/refactor-grc20
moul Jul 7, 2024
d26b7c7
chore: fixup
moul Jul 7, 2024
0ec05b6
chore: fixup
moul Jul 7, 2024
15f13ef
Merge branch 'dev/moul/docs-build' into dev/moul/refactor-grc20
moul Jul 7, 2024
2bcd69f
Merge remote-tracking branch 'origin/master' into dev/moul/refactor-g…
moul Jul 7, 2024
689e42b
chore: fixup
moul Jul 7, 2024
16b9a15
chore: fixup
moul Jul 7, 2024
070f2cb
Merge branch 'master' into dev/moul/atomicswap
moul Jul 7, 2024
6316e48
Merge branch 'dev/moul/refactor-grc20' into dev/moul/atomicswap
moul Jul 7, 2024
3067fa0
chore: fixup
moul Jul 7, 2024
caf38cd
Merge remote-tracking branch 'origin/master' into dev/moul/atomicswap
moul Jul 7, 2024
9df618e
Merge branch 'master' into dev/moul/atomicswap
moul Jul 8, 2024
5343784
chore: fixup
moul Jul 8, 2024
d8bb614
chore: fixup
moul Jul 8, 2024
f1dc45b
chore: fixup
moul Jul 8, 2024
c47a2e8
chore: fixup
moul Jul 8, 2024
79d9404
Merge branch 'master' into dev/moul/atomicswap
moul Jul 8, 2024
7a7c65e
feat: add r/demo/tests/test20
moul Jul 8, 2024
b05e615
Merge remote-tracking branch 'moul/dev/moul/test20' into dev/moul/ato…
moul Jul 8, 2024
b1a2490
feat: add grc20reg that works... today
moul Nov 16, 2024
948bb24
chore: fixup
moul Nov 16, 2024
5c152d2
chore: fixup
moul Nov 16, 2024
b22cd05
chore: fixup
moul Nov 16, 2024
e4249a1
chore: fixup
moul Nov 16, 2024
7110526
chore: fixup
moul Nov 16, 2024
a81f968
chore: fixup
moul Nov 16, 2024
b92dd68
chore: fixup
moul Nov 16, 2024
23127d6
chore: fixup
moul Nov 16, 2024
cddf395
chore: fixup
moul Nov 16, 2024
6435ef0
chore: fixup
moul Nov 16, 2024
9ff36e0
chore: fixup
moul Nov 16, 2024
8a09fd7
chore: fixup
moul Nov 16, 2024
43b46b5
chore: fixup
moul Nov 16, 2024
06c87e5
chore: fixup
moul Nov 16, 2024
0e1b7a7
chore: fixup
moul Nov 16, 2024
28b015a
chore: fixup
moul Nov 16, 2024
8a352d4
chore: fixup
moul Nov 16, 2024
191c779
Merge branch 'dev/moul/grc20reg-v2' into dev/moul/atomicswap
moul Nov 16, 2024
4f21fcb
chore: fixup
moul Nov 16, 2024
f926979
chore: fixup
moul Nov 16, 2024
e28b995
Merge branch 'dev/moul/grc20reg-v2' into dev/moul/atomicswap
moul Nov 16, 2024
f99654e
chore: fixup
moul Nov 16, 2024
3a5acf1
chore: fixup
moul Nov 17, 2024
a21ddd5
chore: fixup
moul Nov 17, 2024
68f8d61
chore: fixup
moul Nov 17, 2024
db1e4a6
Update examples/gno.land/r/demo/grc20reg/grc20reg.gno
moul Nov 18, 2024
4b298d5
chore: fixup
moul Nov 25, 2024
27a2749
chore: fixup
moul Nov 25, 2024
659afe7
Merge branch 'master' into dev/moul/grc20reg-v2
moul Nov 25, 2024
bb88a02
Merge remote-tracking branch 'origin/master' into dev/moul/grc20reg-v2
moul Nov 28, 2024
b662b15
chore: fixup
moul Nov 28, 2024
e3d4c8f
Merge remote-tracking branch 'moul/dev/moul/grc20reg-v2' into dev/mou…
moul Nov 28, 2024
02faa9c
Merge remote-tracking branch 'origin/master' into dev/moul/atomicswap
moul Dec 21, 2024
7a859a8
chore: fixup
moul Dec 21, 2024
90a15aa
chore: fixup
moul Dec 21, 2024
cb84fc5
Merge branch 'dev/moul/grc20reg-getter' into dev/moul/atomicswap
moul Dec 21, 2024
e2dca03
Merge branch 'master' into dev/moul/atomicswap
moul Jan 6, 2025
b7561f2
Merge remote-tracking branch 'origin/master' into dev/moul/atomicswap
moul Jan 6, 2025
93ab638
Update gnovm/stdlibs/std/coins.gno
moul Jan 6, 2025
2a8ef49
Merge branch 'master' into dev/moul/atomicswap
moul Jan 10, 2025
197b3a5
chore: fixup
moul Jan 10, 2025
40ff64a
chore: fixup
moul Jan 10, 2025
8b5f271
Merge remote-tracking branch 'origin/master' into dev/moul/atomicswap
moul Jan 15, 2025
6b5f2d8
Update examples/gno.land/r/demo/atomicswap/atomicswap.gno
moul Jan 15, 2025
e307585
Update gno.land/pkg/integration/testdata/atomicswap.txtar
moul Jan 15, 2025
3ef98ee
Update examples/gno.land/r/demo/atomicswap/atomicswap.gno
moul Jan 15, 2025
4b9509d
chore: package-level doc
moul Jan 15, 2025
7a45b94
chore: fixup
moul Jan 15, 2025
1c4ceaf
chore: fixup
moul Jan 15, 2025
71481e7
Merge branch 'master' into dev/moul/atomicswap
moul Jan 15, 2025
d5f9528
Merge branch 'master' into dev/moul/atomicswap
n2p5 Jan 17, 2025
43f113d
Merge branch 'master' into dev/moul/atomicswap
moul Jan 20, 2025
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
175 changes: 175 additions & 0 deletions examples/gno.land/r/demo/atomicswap/atomicswap.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Package atomicswap implements a hash time-locked contract (HTLC) for atomic swaps
// between native coins (ugnot) or GRC20 tokens.
//
// An atomic swap allows two parties to exchange assets in a trustless way, where
// either both transfers happen or neither does. The process works as follows:
//
// 1. Alice wants to swap with Bob. She generates a secret and creates a swap with
// Bob's address and the hash of the secret (hashlock).
//
// 2. Bob can claim the assets by providing the correct secret before the timelock expires.
// The secret proves Bob knows the preimage of the hashlock.
//
// 3. If Bob doesn't claim in time, Alice can refund the assets back to herself.
//
// Example usage for native coins:
//
// // Alice creates a swap with 1000ugnot for Bob
// secret := "mysecret"
// hashlock := hex.EncodeToString(sha256.Sum256([]byte(secret)))
// id, _ := atomicswap.NewCoinSwap(bobAddr, hashlock) // -send 1000ugnot
//
// // Bob claims the swap by providing the secret
// atomicswap.Claim(id, "mysecret")
//
// Example usage for GRC20 tokens:
//
// // Alice approves the swap contract to spend her tokens
// token.Approve(swapAddr, 1000)
//
// // Alice creates a swap with 1000 tokens for Bob
// id, _ := atomicswap.NewGRC20Swap(bobAddr, hashlock, "gno.land/r/demo/token")
//
// // Bob claims the swap by providing the secret
// atomicswap.Claim(id, "mysecret")
//
// If Bob doesn't claim in time (default 1 week), Alice can refund:
//
// atomicswap.Refund(id)
package atomicswap
n2p5 marked this conversation as resolved.
Show resolved Hide resolved

import (
"std"
"strconv"
"time"

"gno.land/p/demo/avl"
"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/ufmt"
"gno.land/r/demo/grc20reg"
moul marked this conversation as resolved.
Show resolved Hide resolved
)

const defaultTimelockDuration = 7 * 24 * time.Hour // 1w

var (
swaps avl.Tree // id -> *Swap
counter int
)

// NewCoinSwap creates a new atomic swap contract for native coins.
// It uses a default timelock duration.
func NewCoinSwap(recipient std.Address, hashlock string) (int, *Swap) {
timelock := time.Now().Add(defaultTimelockDuration)
return NewCustomCoinSwap(recipient, hashlock, timelock)
}

// NewGRC20Swap creates a new atomic swap contract for grc20 tokens.
// It uses gno.land/r/demo/grc20reg to lookup for a registered token.
func NewGRC20Swap(recipient std.Address, hashlock string, tokenRegistryKey string) (int, *Swap) {
timelock := time.Now().Add(defaultTimelockDuration)
tokenGetter := grc20reg.MustGet(tokenRegistryKey)
token := tokenGetter()
return NewCustomGRC20Swap(recipient, hashlock, timelock, token)
}

// NewCoinSwapWithTimelock creates a new atomic swap contract for native coin.
// It allows specifying a custom timelock duration.
// It is not callable with `gnokey maketx call`, but can be imported by another contract or `gnokey maketx run`.
func NewCustomCoinSwap(recipient std.Address, hashlock string, timelock time.Time) (int, *Swap) {
sender := std.PrevRealm().Addr()
sent := std.GetOrigSend()
require(len(sent) != 0, "at least one coin needs to be sent")
Copy link
Contributor

@leohhhn leohhhn Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is Solidity's style. While I appreciate the conciseness, do you think we should stick to the Go idiomatic way of checking for errors? ie if err:= ...; err!=nil {panic(err)}

Not sure what should be the way forward.

Copy link
Member Author

@moul moul Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is Go too, not very famous, and usually found only in mains or "scripts." I believe it will become more common in Gno realms.

For instance, here is a Go package I wrote several years ago because I was accustomed to having this helper in all my mains: https://pkg.go.dev/moul.io/u#CheckErr


// Create the swap
sendFn := func(to std.Address) {
banker := std.GetBanker(std.BankerTypeRealmSend)
pkgAddr := std.GetOrigPkgAddr()
banker.SendCoins(pkgAddr, to, sent)
}
amountStr := sent.String()
swap := newSwap(sender, recipient, hashlock, timelock, amountStr, sendFn)

counter++
id := strconv.Itoa(counter)
swaps.Set(id, swap)
return counter, swap
}

// NewCustomGRC20Swap creates a new atomic swap contract for grc20 tokens.
// It is not callable with `gnokey maketx call`, but can be imported by another contract or `gnokey maketx run`.
func NewCustomGRC20Swap(recipient std.Address, hashlock string, timelock time.Time, token *grc20.Token) (int, *Swap) {
sender := std.PrevRealm().Addr()
curAddr := std.CurrentRealm().Addr()

allowance := token.Allowance(sender, curAddr)
require(allowance > 0, "no allowance")

userTeller := token.CallerTeller()
err := userTeller.TransferFrom(sender, curAddr, allowance)
require(err == nil, "cannot retrieve tokens from allowance")

amountStr := ufmt.Sprintf("%d%s", allowance, token.GetSymbol())
sendFn := func(to std.Address) {
err := userTeller.Transfer(to, allowance)
require(err == nil, "cannot transfer tokens")
}

swap := newSwap(sender, recipient, hashlock, timelock, amountStr, sendFn)

counter++
id := strconv.Itoa(counter)
swaps.Set(id, swap)

return counter, swap
}

// Claim loads a registered swap and tries to claim it.
func Claim(id int, secret string) {
swap := mustGet(id)
swap.Claim(secret)
}

// Refund loads a registered swap and tries to refund it.
func Refund(id int) {
swap := mustGet(id)
swap.Refund()
}

// Render returns a list of swaps (simplified) for the homepage, and swap details when specifying a swap ID.
func Render(path string) string {
moul marked this conversation as resolved.
Show resolved Hide resolved
if path == "" { // home
output := ""
size := swaps.Size()
max := 10
swaps.ReverseIterateByOffset(size-max, max, func(key string, value interface{}) bool {
swap := value.(*Swap)
output += ufmt.Sprintf("- %s: %s -(%s)> %s - %s\n",
key, swap.sender, swap.amountStr, swap.recipient, swap.Status())
return false
})
return output
} else { // by id
swap, ok := swaps.Get(path)
if !ok {
return "404"
}
return swap.(*Swap).String()
}
Comment on lines +151 to +157
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can be written without the else block enclosing the code

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't fix it myself since I prefer this version to resemble a "switch case" with clear responsibilities. However, if someone believes it's important enough, feel free to send a suggestion that I will approve.

}

// require checks a condition and panics with a message if the condition is false.
func require(check bool, msg string) {
if !check {
panic(msg)
}
}

// mustGet retrieves a swap by its id or panics.
func mustGet(id int) *Swap {
key := strconv.Itoa(id)
swap, ok := swaps.Get(key)
if !ok {
panic("unknown swap ID")
}
return swap.(*Swap)
}
Loading
Loading