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

add univ2 InitPoolSimulator to allow caller to re-use sim instance #330

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
75 changes: 75 additions & 0 deletions pkg/liquidity-source/uniswap-v2/pool_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,81 @@ func NewPoolSimulator(entityPool entity.Pool) (*PoolSimulator, error) {
}, nil
}

func NewPoolSimulatorV2(entityPool entity.Pool) (*PoolSimulator, error) {
sim := &PoolSimulator{}
err := InitPoolSimulator(entityPool, sim)
if err != nil {
return nil, err
}
return sim, nil
}

const NUM_TOKEN = 2

func InitPoolSimulator(entityPool entity.Pool, sim *PoolSimulator) error {
if len(entityPool.Tokens) != NUM_TOKEN || len(entityPool.Reserves) != NUM_TOKEN {
Copy link
Member

Choose a reason for hiding this comment

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

just be cautious, we should do nil check with sim here

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks! 've added a check below

return errors.New("Invalid number of token")
}
// in case the caller mess thing up
if sim == nil {
return errors.New("Invalid simulator instance")
}

var extra Extra
if err := json.Unmarshal([]byte(entityPool.Extra), &extra); err != nil {
return err
}
Comment on lines +84 to +87
Copy link
Member

Choose a reason for hiding this comment

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

Can we reuse Extra?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes we could (by putting Extra into the sim struct), but in this case it's quite small so I skipped it (so that I don't have to change NewPoolSimulator v1)

(this case is quite annoying, extra only used inside this function and nothing refer to it after that, but somehow the compiler still decided to moved it to heap instead, the heap analysis show the reason is the json.Unmarshal call, but I'm still not sure why)


sim.Pool.Info.Address = entityPool.Address
sim.Pool.Info.ReserveUsd = entityPool.ReserveUsd
sim.Pool.Info.Exchange = entityPool.Exchange
sim.Pool.Info.Type = entityPool.Type
sim.Pool.Info.BlockNumber = entityPool.BlockNumber
sim.gas = defaultGas

// try to re-use existing array if possible, if not then allocate new one
if cap(sim.Pool.Info.Tokens) >= NUM_TOKEN {
sim.Pool.Info.Tokens = sim.Pool.Info.Tokens[:NUM_TOKEN]
} else {
sim.Pool.Info.Tokens = make([]string, NUM_TOKEN)
}
for i := range entityPool.Tokens {
sim.Pool.Info.Tokens[i] = entityPool.Tokens[i].Address
}

if cap(sim.Pool.Info.Reserves) >= NUM_TOKEN {
sim.Pool.Info.Reserves = sim.Pool.Info.Reserves[:NUM_TOKEN]
} else {
sim.Pool.Info.Reserves = make([]*big.Int, NUM_TOKEN)
}
var tmp uint256.Int
for i := range entityPool.Reserves {
// still not sure why, but uint256.SetFromDecimal doesn't use `strings.NewReader` like bigInt.SetString
// so it's cheaper to convert string to uint256 then to bigInt
// (in the far future if we can replace pool's reserves with uint256 then we can remove the last step)
err := tmp.SetFromDecimal(entityPool.Reserves[i])
if err != nil {
return err
}
if sim.Pool.Info.Reserves[i] == nil {
sim.Pool.Info.Reserves[i] = new(big.Int)
}
utils.FillBig(&tmp, sim.Pool.Info.Reserves[i])
}

if sim.fee == nil {
sim.fee = new(uint256.Int)
}
sim.fee.SetUint64(extra.Fee)

if sim.feePrecision == nil {
sim.feePrecision = new(uint256.Int)
}
sim.feePrecision.SetUint64(extra.FeePrecision)

return nil
}

func (s *PoolSimulator) CalcAmountOut(param poolpkg.CalcAmountOutParams) (*poolpkg.CalcAmountOutResult, error) {
var (
tokenAmountIn = param.TokenAmountIn
Expand Down
68 changes: 68 additions & 0 deletions pkg/liquidity-source/uniswap-v2/pool_simulator_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package uniswapv2

import (
"fmt"
"math/big"
"testing"

"github.com/goccy/go-json"
"github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/KyberNetwork/blockchain-toolkit/number"

"github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity"
poolpkg "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool"
utils "github.com/KyberNetwork/kyberswap-dex-lib/pkg/util/bignumber"
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/util/testutil"
Expand Down Expand Up @@ -233,3 +237,67 @@ func BenchmarkPoolSimulatorCalcAmountOut(b *testing.B) {
})
}
}

var (
poolRedisBench = `{
"address": "0x3f3ee751ab00246cb0beec2e904ef51e18ac4d77",
"reserveUsd": 0.036380575233191714,
"amplifiedTvl": 0.036380575233191714,
"exchange": "uniswap",
"type": "uniswap-v2",
"timestamp": 1705851986,
"reserves": ["7535596323597", "8596033702378"],
"tokens": [
{ "address": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", "swappable": true },
{ "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "swappable": true }
],
"extra": "{\"fee\":3,\"feePrecision\":1000}",
"blockNumber": 19056224
}`

poolEnt entity.Pool
_ = json.Unmarshal([]byte(poolRedisBench), &poolEnt)
)

func BenchmarkNewPoolSimulator(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = NewPoolSimulator(poolEnt)
}
}

func BenchmarkNewPoolSimulatorV2(b *testing.B) {
sim, _ := NewPoolSimulatorV2(poolEnt)
for i := 0; i < b.N; i++ {
_ = InitPoolSimulator(poolEnt, sim)
}
}

func TestComparePoolSimulatorV2(t *testing.T) {
sim, err := NewPoolSimulator(poolEnt)
require.Nil(t, err)
simV2, err := NewPoolSimulatorV2(poolEnt)
require.Nil(t, err)

for i := 0; i < 500; i++ {
amt := testutil.RandNumberString(15)
t.Run(fmt.Sprintf("test %s", amt), func(t *testing.T) {
in := poolpkg.CalcAmountOutParams{
TokenAmountIn: poolpkg.TokenAmount{
Amount: utils.NewBig(amt),
Token: "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0",
},
TokenOut: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
Limit: nil,
}
res, err := sim.CalcAmountOut(in)
resV2, errV2 := simV2.CalcAmountOut(in)

if err == nil {
require.Nil(t, errV2)
assert.Equal(t, res, resV2)
} else {
require.NotNil(t, errV2)
}
})
}
}
39 changes: 39 additions & 0 deletions pkg/util/bignumber/bignumber.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package bignumber

import (
"encoding/hex"
"math"
"math/big"
"math/bits"

"github.com/holiman/uint256"
)

const MaxWords = 256 / bits.UintSize

var (
// TwoPow128 2^128
TwoPow128 = new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil)
Expand All @@ -16,6 +22,8 @@ var (
Four = big.NewInt(4)
Five = big.NewInt(5)
Six = big.NewInt(6)

MaxU256Hex, _ = hex.DecodeString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
)

var BONE = new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)
Expand All @@ -39,3 +47,34 @@ func NewBig(s string) (res *big.Int) {
res, _ = new(big.Int).SetString(s, 0)
return res
}

// similar to `z.ToBig()` but try to re-use space inside `b` instead of allocating
func FillBig(z *uint256.Int, b *big.Int) {
switch MaxWords { // Compile-time check.
case 4: // 64-bit architectures.
if cap(b.Bits()) < 4 {
// this will resize b, we can be sure that b will only hold at most MaxU256
b.SetBytes(MaxU256Hex)
}
words := b.Bits()[:4]
words[0] = big.Word(z[0])
words[1] = big.Word(z[1])
words[2] = big.Word(z[2])
words[3] = big.Word(z[3])
b.SetBits(words)
case 8: // 32-bit architectures.
if cap(b.Bits()) < 8 {
b.SetBytes(MaxU256Hex)
}
words := b.Bits()[:8]
words[0] = big.Word(z[0])
words[1] = big.Word(z[0] >> 32)
words[2] = big.Word(z[1])
words[3] = big.Word(z[1] >> 32)
words[4] = big.Word(z[2])
words[5] = big.Word(z[2] >> 32)
words[6] = big.Word(z[3])
words[7] = big.Word(z[3] >> 32)
b.SetBits(words)
}
}
15 changes: 15 additions & 0 deletions pkg/util/bignumber/bignumber_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package bignumber

import (
"fmt"
"math/big"
"testing"

"github.com/KyberNetwork/kyberswap-dex-lib/pkg/util/testutil"
"github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
)

Expand All @@ -30,3 +33,15 @@ func TestTenPowDecimals(t *testing.T) {
}

}

func TestFillBig(t *testing.T) {
var bi big.Int
for i := 0; i < 500; i++ {
number := testutil.RandNumberHexString(64)
t.Run(fmt.Sprintf("test %s", number), func(t *testing.T) {
u := uint256.MustFromHex("0x" + number)
FillBig(u, &bi)
assert.Equal(t, u.Dec(), bi.Text(10))
})
}
}
36 changes: 36 additions & 0 deletions pkg/util/testutil/bignumber.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package testutil

import (
"fmt"
"math/rand"
)

func RandNumberString(maxLen int) string {
sLen := rand.Intn(maxLen) + 1
var s string
for i := 0; i < sLen; i++ {
var c int
if i == 0 {
c = rand.Intn(9) + 1
} else {
c = rand.Intn(10)
}
s = fmt.Sprintf("%s%d", s, c)
}
return s
}

func RandNumberHexString(maxLen int) string {
sLen := rand.Intn(maxLen) + 1
var s string
for i := 0; i < sLen; i++ {
var c int
if i == 0 {
c = rand.Intn(15) + 1
} else {
c = rand.Intn(16)
}
s = fmt.Sprintf("%s%x", s, c)
}
return s
}
Loading