Skip to content

Commit

Permalink
Basic geometric twap e2e test (#3835)
Browse files Browse the repository at this point in the history
* feat(x/twap): geometric twap code gen query boilerplate

* revert cli change

* query gen

* wire up API

* test

* fix

* fixes

* add geometric queries

* create pool.json

* add test

* resolve conflict

* fix: swap uosmo in

* fix problem with wallet creation

* updates

* simplify and add comments

* Update tests/e2e/e2e_test.go

* Update tests/e2e/e2e_test.go

* Update tests/e2e/configurer/chain/queries.go

Co-authored-by: Adam Tucker <adam@osmosis.team>

* Update tests/e2e/configurer/chain/queries.go

Co-authored-by: Adam Tucker <adam@osmosis.team>

* Update tests/e2e/e2e_test.go

Co-authored-by: Adam Tucker <adam@osmosis.team>

* Update tests/e2e/e2e_test.go

Co-authored-by: Adam Tucker <adam@osmosis.team>

* Update tests/e2e/e2e_test.go

Co-authored-by: Adam Tucker <adam@osmosis.team>

* Update tests/e2e/e2e_test.go

Co-authored-by: Adam Tucker <adam@osmosis.team>

Co-authored-by: Roman <ackhtariev@gmail.com>
Co-authored-by: Roman <roman@osmosis.team>
Co-authored-by: Adam Tucker <adam@osmosis.team>
  • Loading branch information
4 people committed Jan 4, 2023
1 parent 8196036 commit c7c0222
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 5 deletions.
43 changes: 41 additions & 2 deletions tests/e2e/configurer/chain/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ func (n *NodeConfig) QueryArithmeticTwapToNow(poolId uint64, baseAsset, quoteAss
return sdk.Dec{}, err
}

// nolint: staticcheck
var response twapqueryproto.ArithmeticTwapToNowResponse
err = util.Cdc.UnmarshalJSON(bz, &response)
require.NoError(n.t, err) // this error should not happen
Expand All @@ -209,13 +208,53 @@ func (n *NodeConfig) QueryArithmeticTwap(poolId uint64, baseAsset, quoteAsset st
return sdk.Dec{}, err
}

// nolint: staticcheck
var response twapqueryproto.ArithmeticTwapResponse
err = util.Cdc.UnmarshalJSON(bz, &response)
require.NoError(n.t, err) // this error should not happen
return response.ArithmeticTwap, nil
}

func (n *NodeConfig) QueryGeometricTwapToNow(poolId uint64, baseAsset, quoteAsset string, startTime time.Time) (sdk.Dec, error) {
path := "osmosis/twap/v1beta1/GeometricTwapToNow"

bz, err := n.QueryGRPCGateway(
path,
"pool_id", strconv.FormatInt(int64(poolId), 10),
"base_asset", baseAsset,
"quote_asset", quoteAsset,
"start_time", startTime.Format(time.RFC3339Nano),
)
if err != nil {
return sdk.Dec{}, err
}

var response twapqueryproto.GeometricTwapToNowResponse
err = util.Cdc.UnmarshalJSON(bz, &response)
require.NoError(n.t, err)
return response.GeometricTwap, nil
}

func (n *NodeConfig) QueryGeometricTwap(poolId uint64, baseAsset, quoteAsset string, startTime time.Time, endTime time.Time) (sdk.Dec, error) {
path := "osmosis/twap/v1beta1/GeometricTwap"

bz, err := n.QueryGRPCGateway(
path,
"pool_id", strconv.FormatInt(int64(poolId), 10),
"base_asset", baseAsset,
"quote_asset", quoteAsset,
"start_time", startTime.Format(time.RFC3339Nano),
"end_time", endTime.Format(time.RFC3339Nano),
)
if err != nil {
return sdk.Dec{}, err
}

var response twapqueryproto.GeometricTwapResponse
err = util.Cdc.UnmarshalJSON(bz, &response)
require.NoError(n.t, err)
return response.GeometricTwap, nil
}

// QueryHashFromBlock gets block hash at a specific height. Otherwise, error.
func (n *NodeConfig) QueryHashFromBlock(height int64) (string, error) {
block, err := n.rpcClient.Block(context.Background(), &height)
Expand Down
97 changes: 94 additions & 3 deletions tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,16 +263,16 @@ func (s *IntegrationTestSuite) TestAddToExistingLock() {
chainA.LockAndAddToExistingLock(sdk.NewInt(1000000000000000000), fmt.Sprintf("gamm/pool/%d", poolId), lockupWalletAddr, lockupWalletSuperfluidAddr)
}

// TestTWAP tests TWAP by creating a pool, performing a swap.
// TestArithmeticTWAP tests TWAP by creating a pool, performing a swap.
// These two operations should create TWAP records.
// Then, we wait until the epoch for the records to be pruned.
// The records are guranteed to be pruned at the next epoch
// because twap keep time = epoch time / 4 and we use a timer
// to wait for at least the twap keep time.
func (s *IntegrationTestSuite) TestTWAP() {
func (s *IntegrationTestSuite) TestArithmeticTWAP() {
const (
poolFile = "nativeDenomThreeAssetPool.json"
walletName = "swap-exact-amount-in-wallet"
walletName = "arithmetic-twap-wallet"

denomA = "stake"
denomB = "uion"
Expand Down Expand Up @@ -570,3 +570,94 @@ func (s *IntegrationTestSuite) TestExpeditedProposals() {
s.T().Logf("expeditedVotingPeriodDuration within two seconds of expected time: %v", timeDelta)
close(totalTimeChan)
}

// TestGeometricTWAP tests geometric twap.
// It does the following: creates a pool, queries twap, performs a swap , and queries twap again.
// Twap is expected to change after the swap.
// The pool is created with 1_000_000 uosmo and 2_000_000 stake and equal weights.
// Assuming base asset is uosmo, the initial twap is 2
// Upon swapping 1_000_000 uosmo for stake, supply changes, making uosmo less expensive.
// As a result of the swap, twap changes to 0.5.
func (s *IntegrationTestSuite) TestGeometricTWAP() {
const (
// This pool contains 1_000_000 uosmo and 2_000_000 stake.
// Equals weights.
poolFile = "geometricPool.json"
walletName = "geometric-twap-wallet"

denomA = "uosmo" // 1_000_000 uosmo
denomB = "stake" // 2_000_000 stake

minAmountOut = "1"

epochIdentifier = "day"
)

chainA := s.configurer.GetChainConfig(0)
chainANode, err := chainA.GetDefaultNode()
s.NoError(err)

// Triggers the creation of TWAP records.
poolId := chainANode.CreatePool(poolFile, initialization.ValidatorWalletName)
swapWalletAddr := chainANode.CreateWallet(walletName)

// We add 5 ms to avoid landing directly on block time in twap. If block time
// is provided as start time, the latest spot price is used. Otherwise
// interpolation is done.
timeBeforeSwapPlus5ms := chainANode.QueryLatestBlockTime().Add(5 * time.Millisecond)
// Wait for the next height so that the requested twap
// start time (timeBeforeSwap) is not equal to the block time.
chainA.WaitForNumHeights(1)

s.T().Log("querying for the first geometric TWAP to now (before swap)")
// Assume base = uosmo, quote = stake
// At pool creation time, the twap should be:
// quote assset supply / base asset supply = 2_000_000 / 1_000_000 = 2
initialTwapBOverA, err := chainANode.QueryGeometricTwapToNow(poolId, denomA, denomB, timeBeforeSwapPlus5ms)
s.Require().NoError(err)
s.Require().Equal(sdk.NewDec(2), initialTwapBOverA)

// Assume base = stake, quote = uosmo
// At pool creation time, the twap should be:
// quote assset supply / base asset supply = 1_000_000 / 2_000_000 = 0.5
initialTwapAOverB, err := chainANode.QueryGeometricTwapToNow(poolId, denomB, denomA, timeBeforeSwapPlus5ms)
s.Require().NoError(err)
s.Require().Equal(sdk.NewDecWithPrec(5, 1), initialTwapAOverB)

coinAIn := fmt.Sprintf("1000000%s", denomA)
chainANode.BankSend(coinAIn, chainA.NodeConfigs[0].PublicAddress, swapWalletAddr)

s.T().Logf("performing swap of %s for %s", coinAIn, denomB)

// stake out = stake supply * (1 - (uosmo supply before / uosmo supply after)^(uosmo weight / stake weight))
// = 2_000_000 * (1 - (1_000_000 / 2_000_000)^1)
// = 2_000_000 * 0.5
// = 1_000_000
chainANode.SwapExactAmountIn(coinAIn, minAmountOut, fmt.Sprintf("%d", poolId), denomB, swapWalletAddr)

// New supply post swap:
// stake = 2_000_000 - 1_000_000 - 1_000_000
// uosmo = 1_000_000 + 1_000_000 = 2_000_000

timeAfterSwap := chainANode.QueryLatestBlockTime()
chainA.WaitForNumHeights(1)
timeAfterSwapPlus1Height := chainANode.QueryLatestBlockTime()

s.T().Log("querying for the TWAP from after swap to now")
afterSwapTwapBOverA, err := chainANode.QueryGeometricTwap(poolId, denomA, denomB, timeAfterSwap, timeAfterSwapPlus1Height)
s.Require().NoError(err)

// We swap uosmo so uosmo's supply will increase and stake will decrease.
// The the price after will be smaller than the previous one.
s.Require().True(initialTwapBOverA.GT(afterSwapTwapBOverA))

// Assume base = uosmo, quote = stake
// At pool creation, we had:
// quote assset supply / base asset supply = 2_000_000 / 1_000_000 = 2
// Next, we swapped 1_000_000 uosmo for stake.
// Now, we roughly have
// uatom = 1_000_000
// uosmo = 2_000_000
// quote assset supply / base asset supply = 1_000_000 / 2_000_000 = 0.5
osmoassert.DecApproxEq(s.T(), sdk.NewDecWithPrec(5, 1), afterSwapTwapBOverA, sdk.NewDecWithPrec(1, 2))
}
7 changes: 7 additions & 0 deletions tests/e2e/scripts/geometricPool.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"weights": "1uosmo,1stake",
"initial-deposit": "1000000uosmo,2000000stake",
"swap-fee": "0",
"exit-fee": "0",
"future-governor": ""
}

0 comments on commit c7c0222

Please sign in to comment.