Skip to content

Commit 38314c2

Browse files
authored
feat(pool): improve SwapCallback conventions (#1009)
* feat(pool): improve SwapCallback conventions * test: refactor swap callback parameter datatypes * chore: update swap callback docs * test: fix conventions * fix: parameters * test: fix callback parameters * test: fix callback parameters * test: fix callback parameters * test: fix callback parameters
1 parent ae43d36 commit 38314c2

File tree

56 files changed

+437
-459
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+437
-459
lines changed

contract/r/gnoswap/pool/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,51 @@ Core swap execution (called by Router).
6262
- Calculates fees
6363
- Maintains TWAP oracle
6464

65+
#### Swap Callback
66+
67+
The `Swap` function uses a callback pattern for token transfers, following the Uniswap V3 flash swap design.
68+
69+
**Callback Signature**:
70+
```go
71+
func(cur realm, amount0Delta, amount1Delta int64) error
72+
```
73+
74+
**Delta Convention**:
75+
| Delta | Meaning |
76+
|-------|---------|
77+
| Positive (`> 0`) | Amount the pool must RECEIVE (input token) |
78+
| Negative (`< 0`) | Amount the pool has SENT (output token) |
79+
80+
**Swap Direction Examples**:
81+
82+
For `zeroForOne = true` (token0 → token1):
83+
- `amount0Delta > 0`: Pool receives token0 (input)
84+
- `amount1Delta < 0`: Pool sends token1 (output)
85+
86+
For `zeroForOne = false` (token1 → token0):
87+
- `amount0Delta < 0`: Pool sends token0 (output)
88+
- `amount1Delta > 0`: Pool receives token1 (input)
89+
90+
**Callback Implementation Example**:
91+
```go
92+
func swapCallback(cur realm, amount0Delta, amount1Delta int64) error {
93+
if amount0Delta > 0 {
94+
// Transfer token0 to pool
95+
token0.Transfer(poolAddr, amount0Delta)
96+
}
97+
if amount1Delta > 0 {
98+
// Transfer token1 to pool
99+
token1.Transfer(poolAddr, amount1Delta)
100+
}
101+
return nil
102+
}
103+
```
104+
105+
**Important Notes**:
106+
- The callback MUST transfer at least the positive delta amount to the pool
107+
- Return `nil` on success, or an error to revert the swap
108+
- Pool validates balance increase after callback execution
109+
65110
## Technical Details
66111

67112
### Price Math

contract/r/gnoswap/pool/_mock_test.gno

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func (m *MockPool) Swap(
117117
amountSpecified string,
118118
sqrtPriceLimitX96 string,
119119
payer address,
120-
swapCallback func(cur realm, amount0Delta, amount1Delta string) error,
120+
swapCallback func(cur realm, amount0Delta, amount1Delta int64) error,
121121
) (string, string) {
122122
res, ok := m.Response.Get("Swap")
123123
if !ok {

contract/r/gnoswap/pool/proxy.gno

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ func Swap(
199199
amountSpecified string,
200200
sqrtPriceLimitX96 string,
201201
payer address,
202-
swapCallback func(cur realm, amount0Delta, amount1Delta string) error,
202+
swapCallback func(cur realm, amount0Delta, amount1Delta int64) error,
203203
) (string, string) {
204204
return getImplementation().Swap(
205205
token0Path,

contract/r/gnoswap/pool/types.gno

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ type IPoolSwap interface {
103103
amountSpecified string,
104104
sqrtPriceLimitX96 string,
105105
payer address,
106-
swapCallback func(cur realm, amount0Delta, amount1Delta string) error,
106+
swapCallback func(cur realm, amount0Delta, amount1Delta int64) error,
107107
) (string, string)
108108

109109
SetSwapEndHook(hook func(cur realm, poolPath string) error)

contract/r/gnoswap/pool/upgrade_test.gno

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ func TestSwap(t *testing.T) {
532532
testing.SetRealm(tt.callerRealm)
533533

534534
// Action
535-
Swap(cross, tt.token0Path, tt.token1Path, tt.fee, tt.recipient, tt.zeroForOne, tt.amountSpecified, tt.sqrtPriceLimitX96, tt.caller, func(cur realm, amount0Delta string, amount1Delta string) error {
535+
Swap(cross, tt.token0Path, tt.token1Path, tt.fee, tt.recipient, tt.zeroForOne, tt.amountSpecified, tt.sqrtPriceLimitX96, tt.caller, func(cur realm, amount0Delta int64, amount1Delta int64) error {
536536
return nil
537537
})
538538

contract/r/gnoswap/pool/v1/_helper_test.gno

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package v1
33
import (
44
"chain"
55
"chain/banker"
6-
"strconv"
76
"testing"
87

98
"gno.land/r/gnoland/wugnot"
@@ -614,28 +613,25 @@ func TestBurnTokens(t *testing.T) {
614613
uassert.Equal(t, bar.BalanceOf(addr01), int64(0)) // 100_000_000 -> 0
615614
}
616615

617-
func mockSwapCallback(token0Path string, token1Path string, amount0Delta string, amount1Delta string) error {
616+
func mockSwapCallback(token0Path string, token1Path string, amount0Delta int64, amount1Delta int64) error {
618617
func(cur realm) {
619618
testing.SetRealm(adminRealm)
620619

621-
amount0Int64, _ := strconv.ParseInt(amount0Delta, 10, 64)
622-
amount1Int64, _ := strconv.ParseInt(amount1Delta, 10, 64)
623-
624-
if amount0Int64 > 0 {
620+
if amount0Delta > 0 {
625621
if token0Path == wugnotPath {
626-
testing.SetOriginSend(chain.Coins{{ugnotDenom, amount0Int64}})
622+
testing.SetOriginSend(chain.Coins{{ugnotDenom, amount0Delta}})
627623
wugnot.Deposit(cross)
628624
}
629625

630-
common.SafeGRC20Transfer(cross, token0Path, poolAddr, amount0Int64)
626+
common.SafeGRC20Transfer(cross, token0Path, poolAddr, amount0Delta)
631627
}
632-
if amount1Int64 > 0 {
628+
if amount1Delta > 0 {
633629
if token1Path == wugnotPath {
634-
testing.SetOriginSend(chain.Coins{{ugnotDenom, amount1Int64}})
630+
testing.SetOriginSend(chain.Coins{{ugnotDenom, amount1Delta}})
635631
wugnot.Deposit(cross)
636632
}
637633

638-
common.SafeGRC20Transfer(cross, token1Path, poolAddr, amount1Int64)
634+
common.SafeGRC20Transfer(cross, token1Path, poolAddr, amount1Delta)
639635
}
640636
}(cross)
641637

@@ -781,7 +777,7 @@ func mockInstanceCollectProtocol(token0Path string, token1Path string, fee uint3
781777
}(cross)
782778
}
783779

784-
func mockInstanceSwap(token0Path string, token1Path string, fee uint32, recipient address, zeroForOne bool, amountSpecified string, sqrtPriceLimitX96 string, payer address, swapCallback func(cur realm, amount0Delta string, amount1Delta string) error) (string, string) {
780+
func mockInstanceSwap(token0Path string, token1Path string, fee uint32, recipient address, zeroForOne bool, amountSpecified string, sqrtPriceLimitX96 string, payer address, swapCallback func(cur realm, amount0Delta int64, amount1Delta int64) error) (string, string) {
785781
return func(cur realm) (string, string) {
786782
const currentPoolPath = "gno.land/r/gnoswap/pool"
787783
testing.SetRealm(testing.NewCodeRealm(currentPoolPath))

contract/r/gnoswap/pool/v1/dsl_test.gno

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func (e *PoolTestEnv) WhenSwapExecuted(params SwapParams) (*u256.Uint, *u256.Uin
164164
e.t.Fatalf("Pool not found for %s/%s/%d", params.token0, params.token1, params.fee)
165165
}
166166

167-
swapCallback := func(cur realm, amount0Delta string, amount1Delta string) error {
167+
swapCallback := func(cur realm, amount0Delta int64, amount1Delta int64) error {
168168
testing.SetRealm(adminRealm)
169169
if params.zeroForOne {
170170
common.SafeGRC20Transfer(cross, params.token0, e.currentAddress, math.MaxInt64)

contract/r/gnoswap/pool/v1/pool_test.gno

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ func simulateSwapActivity(t *testing.T, pool *pl.Pool) {
944944
approveTokenForRouter(t, pool.Token0Path())
945945

946946
mockInstanceSwap(pool.Token0Path(), pool.Token1Path(), pool.Fee(),
947-
adminAddr, true, "1000000", "3945129629379410362911094631", adminAddr, func(cur realm, amount0Delta string, amount1Delta string) error {
947+
adminAddr, true, "1000000", "3945129629379410362911094631", adminAddr, func(cur realm, amount0Delta int64, amount1Delta int64) error {
948948
return mockSwapCallback(pool.Token0Path(), pool.Token1Path(), amount0Delta, amount1Delta)
949949
})
950950

contract/r/gnoswap/pool/v1/protocol_fee_test.gno

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -579,17 +579,14 @@ func TestProtocolFeesDisabledWhenNotConfigured(t *testing.T) {
579579
adminAddr,
580580
)
581581

582-
swapCallback := func(cur realm, amount0Delta string, amount1Delta string) error {
582+
swapCallback := func(cur realm, amount0Delta int64, amount1Delta int64) error {
583583
testing.SetRealm(adminRealm)
584584

585-
amount0Int64, _ := strconv.ParseInt(amount0Delta, 10, 64)
586-
amount1Int64, _ := strconv.ParseInt(amount1Delta, 10, 64)
587-
588-
if amount0Int64 > 0 {
589-
common.SafeGRC20Transfer(cross, barPath, poolAddr, amount0Int64)
585+
if amount0Delta > 0 {
586+
common.SafeGRC20Transfer(cross, barPath, poolAddr, amount0Delta)
590587
}
591-
if amount1Int64 > 0 {
592-
common.SafeGRC20Transfer(cross, fooPath, poolAddr, amount1Int64)
588+
if amount1Delta > 0 {
589+
common.SafeGRC20Transfer(cross, fooPath, poolAddr, amount1Delta)
593590
}
594591

595592
return nil

contract/r/gnoswap/pool/v1/swap.gno

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func (i *poolV1) Swap(
132132
amountSpecified string,
133133
sqrtPriceLimitX96 string,
134134
payer address,
135-
swapCallback func(cur realm, amount0Delta, amount1Delta string) error,
135+
swapCallback func(cur realm, amount0Delta, amount1Delta int64) error,
136136
) (string, string) {
137137
halt.AssertIsNotHaltedPool()
138138

@@ -240,15 +240,15 @@ func (i *poolV1) Swap(
240240
if result.Amount1.IsNeg() {
241241
i.safeTransfer(pool, recipient, token1Path, result.Amount1.Abs(), false)
242242

243-
i.safeSwapCallback(pool, token0Path, result.Amount0.Abs(), zeroForOne, swapCallback)
243+
i.safeSwapCallback(pool, token0Path, result.Amount0, result.Amount1, zeroForOne, swapCallback)
244244
}
245245
} else {
246246
// receive token1 from swap callback
247247
// send token0 to recipient (output)
248248
if result.Amount0.IsNeg() {
249249
i.safeTransfer(pool, recipient, token0Path, result.Amount0.Abs(), true)
250250

251-
i.safeSwapCallback(pool, token1Path, result.Amount1.Abs(), zeroForOne, swapCallback)
251+
i.safeSwapCallback(pool, token1Path, result.Amount1, result.Amount0, zeroForOne, swapCallback)
252252
}
253253
}
254254

0 commit comments

Comments
 (0)