Commit 4f99ffdf authored by antstalepresh's avatar antstalepresh
Browse files

Merge branch 'main' of github.com:osmosis-labs/osmosis into auto_staking_some_percentage_lp_rewards

parents b0573313 b0c323c8
Branches unavailable
No related merge requests found
Showing with 435 additions and 208 deletions
+435 -208
......@@ -339,6 +339,9 @@ func NewOsmosisApp(
distrParams.BaseProposerReward = sdk.ZeroDec()
distrParams.BonusProposerReward = sdk.ZeroDec()
app.DistrKeeper.SetParams(ctx, distrParams)
// configure upgrade for gamm module's pool creation fee param add
app.GAMMKeeper.SetParams(ctx, gammtypes.NewParams(sdk.Coins{sdk.NewInt64Coin("uosmo", 1000000000)})) // 1000 OSMO
})
// Create IBC Keeper
......@@ -373,7 +376,7 @@ func NewOsmosisApp(
app.StakingKeeper = *stakingKeeper.SetHooks(
stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks(), app.ClaimKeeper.Hooks()),
)
gammKeeper := gammkeeper.NewKeeper(appCodec, keys[gammtypes.StoreKey], app.AccountKeeper, app.BankKeeper)
gammKeeper := gammkeeper.NewKeeper(appCodec, keys[gammtypes.StoreKey], app.GetSubspace(gammtypes.ModuleName), app.AccountKeeper, app.BankKeeper, app.DistrKeeper)
lockupKeeper := lockupkeeper.NewKeeper(appCodec, keys[lockuptypes.StoreKey], app.AccountKeeper, app.BankKeeper)
epochsKeeper := epochskeeper.NewKeeper(appCodec, keys[epochstypes.StoreKey])
incentivesKeeper := incentiveskeeper.NewKeeper(appCodec, keys[incentivestypes.StoreKey], app.GetSubspace(incentivestypes.ModuleName), app.AccountKeeper, app.BankKeeper, *lockupKeeper, epochsKeeper, app.StakingKeeper)
......@@ -517,6 +520,7 @@ func NewOsmosisApp(
claimtypes.ModuleName,
incentivestypes.ModuleName,
epochstypes.ModuleName,
gammtypes.ModuleName,
)
app.mm.RegisterInvariants(&app.CrisisKeeper)
......@@ -770,6 +774,7 @@ func initParamsKeeper(appCodec codec.BinaryMarshaler, legacyAmino *codec.LegacyA
paramsKeeper.Subspace(ibchost.ModuleName)
paramsKeeper.Subspace(incentivestypes.ModuleName)
paramsKeeper.Subspace(poolincentivestypes.ModuleName)
paramsKeeper.Subspace(gammtypes.ModuleName)
return paramsKeeper
}
package osmomath
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Don't EVER change after initializing
// TODO: Analyze choice here
var powPrecision, _ = sdk.NewDecFromStr("0.00000001")
// Singletons
var zero sdk.Dec = sdk.ZeroDec()
var one_half sdk.Dec = sdk.MustNewDecFromStr("0.5")
var one sdk.Dec = sdk.OneDec()
var two sdk.Dec = sdk.MustNewDecFromStr("2")
/*********************************************************/
// AbsDifferenceWithSign returns | a - b |, (a - b).sign()
// a is mutated and returned
func AbsDifferenceWithSign(a, b sdk.Dec) (sdk.Dec, bool) {
if a.GTE(b) {
return a.SubMut(b), false
} else {
return a.NegMut().AddMut(b), true
}
}
// func largeBasePow(base sdk.Dec, exp sdk.Dec) sdk.Dec {
// // pow requires the base to be <= 2
// }
// Pow computes base^(exp)
// However since the exponent is not an integer, we must do an approximation algorithm.
// TODO: In the future, lets add some optimized routines for common exponents, e.g. for common wIn / wOut ratios
// Many simple exponents like 2:1 pools
func Pow(base sdk.Dec, exp sdk.Dec) sdk.Dec {
// Exponentiation of a negative base with an arbitrary real exponent is not closed within the reals.
// You can see this by recalling that `i = (-1)^(.5)`. We have to go to complex numbers to define this.
// (And would have to implement complex logarithms)
// We don't have a need for negative bases, so we don't include any such logic.
if !base.IsPositive() {
panic(fmt.Errorf("base must be greater than 0"))
}
// TODO: Remove this if we want to generalize the function,
// we can adjust the algorithm in this setting.
if base.GTE(two) {
panic(fmt.Errorf("base must be lesser than two"))
}
// We will use an approximation algorithm to compute the power.
// Since computing an integer power is easy, we split up the exponent into
// an integer component and a fractional component.
integer := exp.TruncateDec()
fractional := exp.Sub(integer)
integerPow := base.Power(uint64(integer.TruncateInt64()))
if fractional.IsZero() {
return integerPow
}
fractionalPow := PowApprox(base, fractional, powPrecision)
return integerPow.Mul(fractionalPow)
}
// Contract: 0 < base <= 2
// 0 < exp < 1
func PowApprox(base sdk.Dec, exp sdk.Dec, precision sdk.Dec) sdk.Dec {
if exp.IsZero() {
return sdk.ZeroDec()
}
// Common case optimization
// Optimize for it being equal to one-half
if exp.Equal(one_half) {
output, err := base.ApproxSqrt()
if err != nil {
panic(err)
}
return output
}
// TODO: Make an approx-equal function, and then check if exp * 3 = 1, and do a check accordingly
// We compute this via taking the maclaurin series of (1 + x)^a
// where x = base - 1.
// The maclaurin series of (1 + x)^a = sum_{k=0}^{infty} binom(a, k) x^k
// Binom(a, k) takes the natural continuation on the first parameter, namely that
// Binom(a, k) = N/D, where D = k!, and N = a(a-1)(a-2)...(a-k+1)
// Next we show that the absolute value of each term is less than the last term.
// Note that the change in term n's value vs term n + 1 is a multiplicative factor of
// v_n = x(a - n) / (n+1)
// So if |v_n| < 1, we know that each term has a lesser impact on the result than the last.
// For our bounds on |x| < 1, |a| < 1,
// it suffices to see for what n is |v_n| < 1,
// in the worst parameterization of x = 1, a = -1.
// v_n = |(-1 + epsilon - n) / (n+1)|
// So |v_n| is always less than 1, as n ranges over the integers.
//
// Note that term_n of the expansion is 1 * prod_{i=0}^{n-1} v_i
// The error if we stop the expansion at term_n is:
// error_n = sum_{k=n+1}^{infty} term_k
// At this point we further restrict a >= 0, so 0 <= a < 1.
// Now we take the _INCORRECT_ assumption that if term_n < p, then
// error_n < p.
// This assumption is obviously wrong.
// However our usages of this function don't use the full domain.
// With a > 0, |x| << 1, and p sufficiently low, perhaps this actually is true.
// TODO: Check with our parameterization
// TODO: If theres a bug, balancer is also wrong here :thonk:
base = base.Clone()
x, xneg := AbsDifferenceWithSign(base, one)
term := sdk.OneDec()
sum := sdk.OneDec()
negative := false
a := exp.Clone()
bigK := sdk.NewDec(0)
// TODO: Document this computation via taylor expansion
for i := int64(1); term.GTE(precision); i++ {
// At each iteration, we need two values, i and i-1.
// To avoid expensive big.Int allocation, we reuse bigK variable.
// On this line, bigK == i-1.
c, cneg := AbsDifferenceWithSign(a, bigK)
// On this line, bigK == i.
bigK.Set(sdk.NewDec(i)) // TODO: O(n) bigint allocation happens
term.MulMut(c).MulMut(x).QuoMut(bigK)
// a is mutated on absDifferenceWithSign, reset
a.Set(exp)
if term.IsZero() {
break
}
if xneg {
negative = !negative
}
if cneg {
negative = !negative
}
if negative {
sum.SubMut(term)
} else {
sum.AddMut(term)
}
}
return sum
}
package osmomath
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)
func TestAbsDifferenceWithSign(t *testing.T) {
decA, err := sdk.NewDecFromStr("3.2")
require.NoError(t, err)
decB, err := sdk.NewDecFromStr("4.3432389")
require.NoError(t, err)
s, b := AbsDifferenceWithSign(decA, decB)
require.True(t, b)
expectedDec, err := sdk.NewDecFromStr("1.1432389")
require.NoError(t, err)
require.Equal(t, expectedDec, s)
}
func TestPowApprox(t *testing.T) {
base, err := sdk.NewDecFromStr("0.8")
require.NoError(t, err)
exp, err := sdk.NewDecFromStr("0.32")
require.NoError(t, err)
s := PowApprox(base, exp, powPrecision)
expectedDec, err := sdk.NewDecFromStr("0.93108385")
require.NoError(t, err)
require.True(
t,
expectedDec.Sub(s).Abs().LTE(powPrecision),
"expected value & actual value's difference should less than precision",
)
}
func TestPow(t *testing.T) {
base, err := sdk.NewDecFromStr("1.68")
require.NoError(t, err)
exp, err := sdk.NewDecFromStr("0.32")
require.NoError(t, err)
s := Pow(base, exp)
expectedDec, err := sdk.NewDecFromStr("1.18058965")
require.NoError(t, err)
require.True(
t,
expectedDec.Sub(s).Abs().LTE(powPrecision),
"expected value & actual value's difference should less than precision",
)
}
package keeper
package osmomath
import (
"testing"
......@@ -56,7 +56,7 @@ func BenchmarkPow(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, test := range tests {
pow(test.base, test.exp)
Pow(test.base, test.exp)
}
}
}
......@@ -80,7 +80,7 @@ func BenchmarkSqrtPow(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, test := range tests {
pow(test.base, one_half)
Pow(test.base, one_half)
}
}
}
......@@ -4,6 +4,17 @@ package osmosis.gamm;
import "gogoproto/gogo.proto";
import "google/protobuf/any.proto";
import "cosmos_proto/cosmos.proto";
import "cosmos/base/v1beta1/coin.proto";
// Params holds parameters for the incentives module
message Params {
repeated cosmos.base.v1beta1.Coin pool_creation_fee = 1 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.moretags) = "yaml:\"pool_creation_fee\"",
(gogoproto.nullable) = false
];
}
option go_package = "github.com/osmosis-labs/osmosis/x/gamm/types";
......@@ -12,4 +23,5 @@ message GenesisState {
repeated google.protobuf.Any pools = 1
[ (cosmos_proto.accepts_interface) = "PoolI" ];
uint64 next_pool_number = 2;
Params params = 3 [ (gogoproto.nullable) = false ];
}
......@@ -51,7 +51,8 @@ func TestEpochsInitGenesis(t *testing.T) {
ctx = ctx.WithBlockHeight(1)
ctx = ctx.WithBlockTime(now)
epochs.InitGenesis(ctx, app.EpochsKeeper, types.GenesisState{
//test genesisState validation
genesisState := types.GenesisState{
Epochs: []types.EpochInfo{
{
Identifier: "monthly",
......@@ -62,9 +63,34 @@ func TestEpochsInitGenesis(t *testing.T) {
EpochCountingStarted: true,
CurrentEpochEnded: true,
},
{
Identifier: "monthly",
StartTime: time.Time{},
Duration: time.Hour * 24,
CurrentEpoch: 0,
CurrentEpochStartTime: time.Time{},
EpochCountingStarted: true,
CurrentEpochEnded: true,
},
},
})
}
require.EqualError(t, genesisState.Validate(), "epoch identifier should be unique")
genesisState = types.GenesisState{
Epochs: []types.EpochInfo{
{
Identifier: "monthly",
StartTime: time.Time{},
Duration: time.Hour * 24,
CurrentEpoch: 0,
CurrentEpochStartTime: time.Time{},
EpochCountingStarted: true,
CurrentEpochEnded: true,
},
},
}
epochs.InitGenesis(ctx, app.EpochsKeeper, genesisState)
epochInfo := app.EpochsKeeper.GetEpochInfo(ctx, "monthly")
require.Equal(t, epochInfo.Identifier, "monthly")
require.Equal(t, epochInfo.StartTime.UTC().String(), now.UTC().String())
......
......@@ -41,13 +41,18 @@ func DefaultGenesis() *GenesisState {
// failure.
func (gs GenesisState) Validate() error {
// TODO: Epochs identifiers should be unique
epochIdentifiers := map[string]bool{}
for _, epoch := range gs.Epochs {
if epoch.Identifier == "" {
return errors.New("epoch identifier should NOT be empty")
}
if epochIdentifiers[epoch.Identifier] {
return errors.New("epoch identifier should be unique")
}
if epoch.Duration == 0 {
return errors.New("epoch duration should NOT be 0")
}
epochIdentifiers[epoch.Identifier] = true
}
return nil
}
package types
import (
"fmt"
)
func ValidateEpochIdentifierInterface(i interface{}) error {
v, ok := i.(string)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
ValidateEpochIdentifierString(v)
return nil
}
func ValidateEpochIdentifierString(s string) error {
if s == "" {
return fmt.Errorf("empty distribution epoch identifier: %+v", s)
}
return nil
}
......@@ -19,7 +19,7 @@ import (
"github.com/osmosis-labs/osmosis/x/gamm/client/cli"
gammtestutil "github.com/osmosis-labs/osmosis/x/gamm/client/testutil"
"github.com/osmosis-labs/osmosis/x/gamm/types"
gammtypes "github.com/osmosis-labs/osmosis/x/gamm/types"
tmcli "github.com/tendermint/tendermint/libs/cli"
)
......@@ -35,6 +35,16 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.cfg = app.DefaultConfig()
encCfg := app.MakeEncodingConfig()
// modification to pay fee with test bond denom "stake"
genesisState := app.ModuleBasics.DefaultGenesis(encCfg.Marshaler)
gammGen := gammtypes.DefaultGenesis()
gammGen.Params.PoolCreationFee = sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 1000000)}
gammGenJson := encCfg.Marshaler.MustMarshalJSON(gammGen)
genesisState[gammtypes.ModuleName] = gammGenJson
s.cfg.GenesisState = genesisState
s.network = network.New(s.T(), s.cfg)
_, err := s.network.WaitForHeight(1)
......@@ -67,7 +77,7 @@ func (s *IntegrationTestSuite) TestNewCreatePoolCmd() {
val.ClientCtx,
val.Address,
newAddr,
sdk.NewCoins(sdk.NewInt64Coin(s.cfg.BondDenom, 20000), sdk.NewInt64Coin("node0token", 20000)), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
sdk.NewCoins(sdk.NewInt64Coin(s.cfg.BondDenom, 200000000), sdk.NewInt64Coin("node0token", 20000)), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
)
......
......@@ -119,8 +119,13 @@ $ %s query gamm pools
}
queryClient := types.NewQueryClient(clientCtx)
pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}
res, err := queryClient.Pools(cmd.Context(), &types.QueryPoolsRequest{
Pagination: nil,
Pagination: pageReq,
})
if err != nil {
return err
......@@ -131,6 +136,7 @@ $ %s query gamm pools
}
flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "pools")
return cmd
}
......
......@@ -12,6 +12,7 @@ import (
// InitGenesis initializes the capability module's state from a provided genesis
// state.
func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState, unpacker codectypes.AnyUnpacker) {
k.SetParams(ctx, genState.Params)
k.SetNextPoolNumber(ctx, genState.NextPoolNumber)
liquidity := sdk.Coins{}
......@@ -56,5 +57,6 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState {
return &types.GenesisState{
NextPoolNumber: k.GetNextPoolNumber(ctx),
Pools: poolAnys,
Params: k.GetParams(ctx),
}
}
......@@ -6,6 +6,7 @@ import (
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
simapp "github.com/osmosis-labs/osmosis/app"
appparams "github.com/osmosis-labs/osmosis/app/params"
"github.com/osmosis-labs/osmosis/x/gamm"
"github.com/osmosis-labs/osmosis/x/gamm/types"
"github.com/stretchr/testify/assert"
......@@ -42,6 +43,9 @@ func TestGammInitGenesis(t *testing.T) {
gamm.InitGenesis(ctx, app.GAMMKeeper, types.GenesisState{
Pools: []*codectypes.Any{any},
NextPoolNumber: 2,
Params: types.Params{
PoolCreationFee: sdk.Coins{sdk.NewInt64Coin(appparams.BaseCoinUnit, 1000_000_000)},
},
}, app.AppCodec())
require.Equal(t, app.GAMMKeeper.GetNextPoolNumber(ctx), uint64(2))
......@@ -68,6 +72,7 @@ func TestGammExportGenesis(t *testing.T) {
acc1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes())
app.BankKeeper.SetBalances(ctx, acc1, sdk.Coins{
sdk.NewCoin("uosmo", sdk.NewInt(10000000000)),
sdk.NewInt64Coin("foo", 100000),
sdk.NewInt64Coin("bar", 100000),
})
......@@ -110,6 +115,7 @@ func TestMarshalUnmarshalGenesis(t *testing.T) {
am := gamm.NewAppModule(appCodec, app.GAMMKeeper, app.AccountKeeper, app.BankKeeper)
acc1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes())
app.BankKeeper.SetBalances(ctx, acc1, sdk.Coins{
sdk.NewCoin("uosmo", sdk.NewInt(10000000000)),
sdk.NewInt64Coin("foo", 100000),
sdk.NewInt64Coin("bar", 100000),
})
......
......@@ -6,6 +6,8 @@ import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/osmomath"
"github.com/osmosis-labs/osmosis/x/gamm/types"
)
......@@ -88,9 +90,9 @@ func PoolTotalWeightInvariant(keeper Keeper, bk types.BankKeeper) sdk.Invariant
func genericPow(base, exp sdk.Dec) sdk.Dec {
if !base.GTE(sdk.NewDec(2)) {
return pow(base, exp)
return osmomath.Pow(base, exp)
}
return powApprox(sdk.OneDec().Quo(base), exp.Neg(), powPrecision)
return osmomath.PowApprox(sdk.OneDec().Quo(base), exp.Neg(), powPrecision)
}
// constantChange returns the multiplicative factor difference in the pool constant, between two different pools.
......
......@@ -7,6 +7,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/osmosis-labs/osmosis/x/gamm/types"
)
......@@ -24,14 +25,16 @@ type Keeper struct {
storeKey sdk.StoreKey
cdc codec.BinaryMarshaler
hooks types.GammHooks
paramSpace paramtypes.Subspace
hooks types.GammHooks
// keepers
accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
distrKeeper types.DistrKeeper
}
func NewKeeper(cdc codec.BinaryMarshaler, storeKey sdk.StoreKey, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper) Keeper {
func NewKeeper(cdc codec.BinaryMarshaler, storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, distrKeeper types.DistrKeeper) Keeper {
// Ensure that the module account are set.
moduleAddr, perms := accountKeeper.GetModuleAddressAndPermissions(types.ModuleName)
if moduleAddr == nil {
......@@ -43,12 +46,17 @@ func NewKeeper(cdc codec.BinaryMarshaler, storeKey sdk.StoreKey, accountKeeper t
if !permContains(perms, authtypes.Burner) {
panic(fmt.Sprintf("%s module account should have the burner permission", types.ModuleName))
}
if !paramSpace.HasKeyTable() {
paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable())
}
return Keeper{
storeKey: storeKey,
cdc: cdc,
storeKey: storeKey,
cdc: cdc,
paramSpace: paramSpace,
// keepers
accountKeeper: accountKeeper,
bankKeeper: bankKeeper,
distrKeeper: distrKeeper,
}
}
......
......@@ -50,6 +50,7 @@ func (suite *KeeperTestSuite) preparePoolWithPoolParams(poolParams types.PoolPar
suite.ctx,
acc,
sdk.NewCoins(
sdk.NewCoin("uosmo", sdk.NewInt(10000000000)),
sdk.NewCoin("foo", sdk.NewInt(10000000)),
sdk.NewCoin("bar", sdk.NewInt(10000000)),
sdk.NewCoin("baz", sdk.NewInt(10000000)),
......
package keeper
import (
"fmt"
"github.com/osmosis-labs/osmosis/osmomath"
sdk "github.com/cosmos/cosmos-sdk/types"
)
......@@ -67,7 +67,7 @@ func calcOutGivenIn(
adjustedIn := sdk.OneDec().Sub(swapFee)
adjustedIn = tokenAmountIn.Mul(adjustedIn)
y := tokenBalanceIn.Quo(tokenBalanceIn.Add(adjustedIn))
foo := pow(y, weightRatio)
foo := osmomath.Pow(y, weightRatio)
bar := sdk.OneDec().Sub(foo)
return tokenBalanceOut.Mul(bar)
}
......@@ -84,7 +84,7 @@ func calcInGivenOut(
weightRatio := tokenWeightOut.Quo(tokenWeightIn)
diff := tokenBalanceOut.Sub(tokenAmountOut)
y := tokenBalanceOut.Quo(diff)
foo := pow(y, weightRatio)
foo := osmomath.Pow(y, weightRatio)
foo = foo.Sub(one)
tokenAmountIn := sdk.OneDec().Sub(swapFee)
return (tokenBalanceIn.Mul(foo)).Quo(tokenAmountIn)
......@@ -108,7 +108,7 @@ func calcPoolOutGivenSingleIn(
tokenInRatio := newTokenBalanceIn.Quo(tokenBalanceIn)
// uint newPoolSupply = (ratioTi ^ weightTi) * poolSupply;
poolRatio := pow(tokenInRatio, normalizedWeight)
poolRatio := osmomath.Pow(tokenInRatio, normalizedWeight)
newPoolSupply := poolRatio.Mul(poolSupply)
return newPoolSupply.Sub(poolSupply)
}
......@@ -128,7 +128,7 @@ func calcSingleInGivenPoolOut(
//uint newBalTi = poolRatio^(1/weightTi) * balTi;
boo := sdk.OneDec().Quo(normalizedWeight)
tokenInRatio := pow(poolRatio, boo)
tokenInRatio := osmomath.Pow(poolRatio, boo)
newTokenBalanceIn := tokenInRatio.Mul(tokenBalanceIn)
tokenAmountInAfterFee := newTokenBalanceIn.Sub(tokenBalanceIn)
// Do reverse order of fees charged in joinswap_ExternAmountIn, this way
......@@ -157,7 +157,7 @@ func calcSingleOutGivenPoolIn(
// newBalTo = poolRatio^(1/weightTo) * balTo;
tokenOutRatio := pow(poolRatio, sdk.OneDec().Quo(normalizedWeight))
tokenOutRatio := osmomath.Pow(poolRatio, sdk.OneDec().Quo(normalizedWeight))
newTokenBalanceOut := tokenOutRatio.Mul(tokenBalanceOut)
tokenAmountOutBeforeSwapFee := tokenBalanceOut.Sub(newTokenBalanceOut)
......@@ -190,7 +190,7 @@ func calcPoolInGivenSingleOut(
tokenOutRatio := newTokenBalanceOut.Quo(tokenBalanceOut)
//uint newPoolSupply = (ratioTo ^ weightTo) * poolSupply;
poolRatio := pow(tokenOutRatio, normalizedWeight)
poolRatio := osmomath.Pow(tokenOutRatio, normalizedWeight)
newPoolSupply := poolRatio.Mul(poolSupply)
poolAmountInAfterExitFee := poolSupply.Sub(newPoolSupply)
......@@ -198,141 +198,3 @@ func calcPoolInGivenSingleOut(
// pAi = pAiAfterExitFee/(1-exitFee)
return poolAmountInAfterExitFee.Quo(sdk.OneDec().Sub(exitFee))
}
/*********************************************************/
// absDifferenceWithSign returns | a - b |, (a - b).sign()
// a is mutated and returned
func absDifferenceWithSign(a, b sdk.Dec) (sdk.Dec, bool) {
if a.GTE(b) {
return a.SubMut(b), false
} else {
return a.NegMut().AddMut(b), true
}
}
// func largeBasePow(base sdk.Dec, exp sdk.Dec) sdk.Dec {
// // pow requires the base to be <= 2
// }
// pow computes base^(exp)
// However since the exponent is not an integer, we must do an approximation algorithm.
// TODO: In the future, lets add some optimized routines for common exponents, e.g. for common wIn / wOut ratios
// Many simple exponents like 2:1 pools
func pow(base sdk.Dec, exp sdk.Dec) sdk.Dec {
// Exponentiation of a negative base with an arbitrary real exponent is not closed within the reals.
// You can see this by recalling that `i = (-1)^(.5)`. We have to go to complex numbers to define this.
// (And would have to implement complex logarithms)
// We don't have a need for negative bases, so we don't include any such logic.
if !base.IsPositive() {
panic(fmt.Errorf("base must be greater than 0"))
}
// TODO: Remove this if we want to generalize the function,
// we can adjust the algorithm in this setting.
if base.GTE(two) {
panic(fmt.Errorf("base must be lesser than two"))
}
// We will use an approximation algorithm to compute the power.
// Since computing an integer power is easy, we split up the exponent into
// an integer component and a fractional component.
integer := exp.TruncateDec()
fractional := exp.Sub(integer)
integerPow := base.Power(uint64(integer.TruncateInt64()))
if fractional.IsZero() {
return integerPow
}
fractionalPow := powApprox(base, fractional, powPrecision)
return integerPow.Mul(fractionalPow)
}
// Contract: 0 < base <= 2
// 0 < exp < 1
func powApprox(base sdk.Dec, exp sdk.Dec, precision sdk.Dec) sdk.Dec {
if exp.IsZero() {
return sdk.ZeroDec()
}
// Common case optimization
// Optimize for it being equal to one-half
if exp.Equal(one_half) {
output, err := base.ApproxSqrt()
if err != nil {
panic(err)
}
return output
}
// TODO: Make an approx-equal function, and then check if exp * 3 = 1, and do a check accordingly
// We compute this via taking the maclaurin series of (1 + x)^a
// where x = base - 1.
// The maclaurin series of (1 + x)^a = sum_{k=0}^{infty} binom(a, k) x^k
// Binom(a, k) takes the natural continuation on the first parameter, namely that
// Binom(a, k) = N/D, where D = k!, and N = a(a-1)(a-2)...(a-k+1)
// Next we show that the absolute value of each term is less than the last term.
// Note that the change in term n's value vs term n + 1 is a multiplicative factor of
// v_n = x(a - n) / (n+1)
// So if |v_n| < 1, we know that each term has a lesser impact on the result than the last.
// For our bounds on |x| < 1, |a| < 1,
// it suffices to see for what n is |v_n| < 1,
// in the worst parameterization of x = 1, a = -1.
// v_n = |(-1 + epsilon - n) / (n+1)|
// So |v_n| is always less than 1, as n ranges over the integers.
//
// Note that term_n of the expansion is 1 * prod_{i=0}^{n-1} v_i
// The error if we stop the expansion at term_n is:
// error_n = sum_{k=n+1}^{infty} term_k
// At this point we further restrict a >= 0, so 0 <= a < 1.
// Now we take the _INCORRECT_ assumption that if term_n < p, then
// error_n < p.
// This assumption is obviously wrong.
// However our usages of this function don't use the full domain.
// With a > 0, |x| << 1, and p sufficiently low, perhaps this actually is true.
// TODO: Check with our parameterization
// TODO: If theres a bug, balancer is also wrong here :thonk:
base = base.Clone()
x, xneg := absDifferenceWithSign(base, one)
term := sdk.OneDec()
sum := sdk.OneDec()
negative := false
a := exp.Clone()
bigK := sdk.NewDec(0)
// TODO: Document this computation via taylor expansion
for i := int64(1); term.GTE(precision); i++ {
// At each iteration, we need two values, i and i-1.
// To avoid expensive big.Int allocation, we reuse bigK variable.
// On this line, bigK == i-1.
c, cneg := absDifferenceWithSign(a, bigK)
// On this line, bigK == i.
bigK.Set(sdk.NewDec(i)) // TODO: O(n) bigint allocation happens
term.MulMut(c).MulMut(x).QuoMut(bigK)
// a is mutated on absDifferenceWithSign, reset
a.Set(exp)
if term.IsZero() {
break
}
if xneg {
negative = !negative
}
if cneg {
negative = !negative
}
if negative {
sum.SubMut(term)
} else {
sum.AddMut(term)
}
}
return sum
}
......@@ -8,54 +8,6 @@ import (
"github.com/stretchr/testify/require"
)
func TestAbsDifferenceWithSign(t *testing.T) {
decA, err := sdk.NewDecFromStr("3.2")
require.NoError(t, err)
decB, err := sdk.NewDecFromStr("4.3432389")
require.NoError(t, err)
s, b := absDifferenceWithSign(decA, decB)
require.True(t, b)
expectedDec, err := sdk.NewDecFromStr("1.1432389")
require.NoError(t, err)
require.Equal(t, expectedDec, s)
}
func TestPowApprox(t *testing.T) {
base, err := sdk.NewDecFromStr("0.8")
require.NoError(t, err)
exp, err := sdk.NewDecFromStr("0.32")
require.NoError(t, err)
s := powApprox(base, exp, powPrecision)
expectedDec, err := sdk.NewDecFromStr("0.93108385")
require.NoError(t, err)
require.True(
t,
expectedDec.Sub(s).Abs().LTE(powPrecision),
"expected value & actual value's difference should less than precision",
)
}
func TestPow(t *testing.T) {
base, err := sdk.NewDecFromStr("1.68")
require.NoError(t, err)
exp, err := sdk.NewDecFromStr("0.32")
require.NoError(t, err)
s := pow(base, exp)
expectedDec, err := sdk.NewDecFromStr("1.18058965")
require.NoError(t, err)
require.True(
t,
expectedDec.Sub(s).Abs().LTE(powPrecision),
"expected value & actual value's difference should less than precision",
)
}
func TestCalcSpotPrice(t *testing.T) {
// TODO: Change test to be table driven
tokenBalanceIn, err := sdk.NewDecFromStr("100")
......
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/x/gamm/types"
)
// GetParams returns the total set params
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
k.paramSpace.GetParamSet(ctx, &params)
return params
}
// SetParams sets the total set of params
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramSpace.SetParamSet(ctx, &params)
}
......@@ -28,6 +28,13 @@ func (k Keeper) CreatePool(
)
}
// send pool creation fee to community pool
params := k.GetParams(ctx)
err := k.distrKeeper.FundCommunityPool(ctx, params.PoolCreationFee, sender)
if err != nil {
return 0, err
}
pool, err := k.newPool(ctx, poolParams, poolAssets, futurePoolGovernor)
if err != nil {
return 0, err
......
......@@ -19,6 +19,13 @@ var (
)
func (suite *KeeperTestSuite) TestCreatePool() {
params := suite.app.GAMMKeeper.GetParams(suite.ctx)
poolCreationFeeDecCoins := sdk.DecCoins{}
for _, coin := range params.PoolCreationFee {
poolCreationFeeDecCoins = poolCreationFeeDecCoins.Add(sdk.NewDecCoin(coin.Denom, coin.Amount))
}
func() {
keeper := suite.app.GAMMKeeper
......@@ -41,6 +48,8 @@ func (suite *KeeperTestSuite) TestCreatePool() {
}{{
fn: func() {
keeper := suite.app.GAMMKeeper
prevFeePool := suite.app.DistrKeeper.GetFeePoolCommunityCoins(suite.ctx)
prevAcc1Bal := suite.app.BankKeeper.GetAllBalances(suite.ctx, acc1)
poolId, err := keeper.CreatePool(suite.ctx, acc1, types.PoolParams{
SwapFee: sdk.NewDecWithPrec(1, 2),
ExitFee: sdk.NewDecWithPrec(1, 2),
......@@ -59,6 +68,20 @@ func (suite *KeeperTestSuite) TestCreatePool() {
fmt.Sprintf("share token should be minted as %s initially", types.InitPoolSharesSupply.String()),
)
// check fee is correctly sent to community pool
feePool := suite.app.DistrKeeper.GetFeePoolCommunityCoins(suite.ctx)
suite.Require().Equal(feePool, prevFeePool.Add(poolCreationFeeDecCoins...))
// check account's balance is correctly reduced
acc1Bal := suite.app.BankKeeper.GetAllBalances(suite.ctx, acc1)
suite.Require().Equal(acc1Bal.String(),
prevAcc1Bal.Sub(params.PoolCreationFee).
Sub(sdk.Coins{
sdk.NewCoin("bar", sdk.NewInt(10000)),
sdk.NewCoin("foo", sdk.NewInt(10000)),
}).Add(sdk.NewCoin(types.GetPoolShareDenom(pool.GetId()), types.InitPoolSharesSupply)).String(),
)
liquidity := suite.app.GAMMKeeper.GetTotalLiquidity(suite.ctx)
suite.Require().Equal("10000bar,10000foo", liquidity.String())
},
......@@ -183,6 +206,48 @@ func (suite *KeeperTestSuite) TestCreatePool() {
}}, defaultFutureGovernor)
suite.Require().Error(err, "can't create the pool with duplicated PoolAssets")
},
}, {
fn: func() {
keeper := suite.app.GAMMKeeper
keeper.SetParams(suite.ctx, types.Params{
PoolCreationFee: sdk.Coins{},
})
_, err := keeper.CreatePool(suite.ctx, acc1, types.PoolParams{
SwapFee: sdk.NewDecWithPrec(1, 2),
ExitFee: sdk.NewDecWithPrec(1, 2),
}, []types.PoolAsset{{
Weight: sdk.NewInt(100),
Token: sdk.NewCoin("foo", sdk.NewInt(10000)),
}, {
Weight: sdk.NewInt(100),
Token: sdk.NewCoin("bar", sdk.NewInt(10000)),
}}, defaultFutureGovernor)
suite.Require().NoError(err)
pools, err := keeper.GetPools(suite.ctx)
suite.Require().Len(pools, 1)
suite.Require().NoError(err)
},
}, {
fn: func() {
keeper := suite.app.GAMMKeeper
keeper.SetParams(suite.ctx, types.Params{
PoolCreationFee: nil,
})
_, err := keeper.CreatePool(suite.ctx, acc1, types.PoolParams{
SwapFee: sdk.NewDecWithPrec(1, 2),
ExitFee: sdk.NewDecWithPrec(1, 2),
}, []types.PoolAsset{{
Weight: sdk.NewInt(100),
Token: sdk.NewCoin("foo", sdk.NewInt(10000)),
}, {
Weight: sdk.NewInt(100),
Token: sdk.NewCoin("bar", sdk.NewInt(10000)),
}}, defaultFutureGovernor)
suite.Require().NoError(err)
pools, err := keeper.GetPools(suite.ctx)
suite.Require().Len(pools, 1)
suite.Require().NoError(err)
},
}}
for _, test := range tests {
......@@ -194,6 +259,7 @@ func (suite *KeeperTestSuite) TestCreatePool() {
suite.ctx,
acc,
sdk.NewCoins(
sdk.NewCoin("uosmo", sdk.NewInt(10000000000)),
sdk.NewCoin("foo", sdk.NewInt(10000000)),
sdk.NewCoin("bar", sdk.NewInt(10000000)),
sdk.NewCoin("baz", sdk.NewInt(10000000)),
......@@ -281,6 +347,7 @@ func (suite *KeeperTestSuite) TestJoinPool() {
suite.ctx,
acc,
sdk.NewCoins(
sdk.NewCoin("uosmo", sdk.NewInt(10000000000)),
sdk.NewCoin("foo", sdk.NewInt(10000000)),
sdk.NewCoin("bar", sdk.NewInt(10000000)),
sdk.NewCoin("baz", sdk.NewInt(10000000)),
......@@ -389,6 +456,7 @@ func (suite *KeeperTestSuite) TestExitPool() {
suite.ctx,
acc,
sdk.NewCoins(
sdk.NewCoin("uosmo", sdk.NewInt(10000000000)),
sdk.NewCoin("foo", sdk.NewInt(10000000)),
sdk.NewCoin("bar", sdk.NewInt(10000000)),
sdk.NewCoin("baz", sdk.NewInt(10000000)),
......@@ -436,6 +504,7 @@ func (suite *KeeperTestSuite) TestActivePool() {
suite.ctx,
acc,
sdk.NewCoins(
sdk.NewCoin("uosmo", sdk.NewInt(10000000000)),
sdk.NewCoin("foo", sdk.NewInt(10000000)),
sdk.NewCoin("bar", sdk.NewInt(10000000)),
sdk.NewCoin("baz", sdk.NewInt(10000000)),
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment