...
 
Commits (4)
......@@ -58,7 +58,7 @@ func (c Coin) IsValid() error {
}
func (c Coin) String() string {
return fmt.Sprintf("%s%s", c.Asset.String(), c.Amount.String())
return fmt.Sprintf("%s%s", c.Amount.String(), c.Asset.String())
}
func (cs Coins) IsValid() error {
......
......@@ -211,7 +211,7 @@ func newOutboundTxHandlerTestHelper(c *C) outboundTxHandlerTestHelper {
Chain: common.BNBChain,
ToAddress: tx.Tx.FromAddress,
VaultPubKey: yggVault.PubKey,
Coin: common.NewCoin(common.RuneAsset(), sdk.NewUint(common.One)),
Coin: common.NewCoin(common.RuneAsset(), sdk.NewUint(2*common.One)),
Memo: NewOutboundMemo(tx.Tx.ID).String(),
InHash: tx.Tx.ID,
}
......
......@@ -131,7 +131,7 @@ func newRefundTxHandlerTestHelper(c *C) refundTxHandlerTestHelper {
tx := NewObservedTx(common.Tx{
ID: GetRandomTxHash(),
Chain: common.BNBChain,
Coins: common.Coins{common.NewCoin(common.BNBAsset, sdk.NewUint(1*common.One))},
Coins: common.Coins{common.NewCoin(common.BNBAsset, sdk.NewUint(2*common.One))},
Memo: "swap:RUNE-A1F",
FromAddress: GetRandomBNBAddress(),
ToAddress: addr,
......@@ -157,7 +157,7 @@ func newRefundTxHandlerTestHelper(c *C) refundTxHandlerTestHelper {
Chain: common.BNBChain,
ToAddress: tx.Tx.FromAddress,
VaultPubKey: yggVault.PubKey,
Coin: common.NewCoin(common.RuneAsset(), sdk.NewUint(common.One)),
Coin: common.NewCoin(common.RuneAsset(), sdk.NewUint(2*common.One)),
Memo: NewRefundMemo(tx.Tx.ID).String(),
InHash: tx.Tx.ID,
}
......@@ -433,19 +433,19 @@ func (s *HandlerRefundSuite) TestOutboundTxHandlerSendAdditionalCoinsShouldBeSla
Chain: common.BNBChain,
Coins: common.Coins{
common.NewCoin(common.RuneAsset(), sdk.NewUint(1*common.One)),
common.NewCoin(common.BNBAsset, sdk.NewUint(1*common.One)),
common.NewCoin(common.BNBAsset, sdk.NewUint(common.One)),
},
Memo: NewRefundMemo(helper.inboundTx.Tx.ID).String(),
FromAddress: fromAddr,
ToAddress: helper.inboundTx.Tx.FromAddress,
Gas: common.BNBGasFeeSingleton,
}, helper.ctx.BlockHeight(), helper.nodeAccount.PubKeySet.Secp256k1)
expectedBond := helper.nodeAccount.Bond.Sub(sdk.NewUint(common.One).MulUint64(3).QuoUint64(2))
expectedBond := helper.nodeAccount.Bond.Sub(sdk.NewUint(1 * common.One).MulUint64(3).QuoUint64(2))
// slash one BNB
outMsg := NewMsgRefundTx(tx, helper.inboundTx.Tx.ID, helper.nodeAccount.NodeAddress)
c.Assert(handler.Run(helper.ctx, outMsg, semver.MustParse("0.1.0"), helper.constAccessor).Code, Equals, sdk.CodeOK)
na, err := helper.keeper.GetNodeAccount(helper.ctx, helper.nodeAccount.NodeAddress)
c.Assert(na.Bond.Equal(expectedBond), Equals, true)
c.Assert(na.Bond.Equal(expectedBond), Equals, true, Commentf("Bond: %d != %d", na.Bond.Uint64(), expectedBond.Uint64()))
}
func (s *HandlerRefundSuite) TestOutboundTxHandlerInvalidObservedTxVoterShouldSlash(c *C) {
......@@ -481,7 +481,7 @@ func (s *HandlerRefundSuite) TestOutboundTxHandlerInvalidObservedTxVoterShouldSl
outMsg := NewMsgRefundTx(tx, tx.Tx.ID, helper.nodeAccount.NodeAddress)
c.Assert(handler.Run(helper.ctx, outMsg, semver.MustParse("0.1.0"), helper.constAccessor).Code, Equals, sdk.CodeOK)
na, err := helper.keeper.GetNodeAccount(helper.ctx, helper.nodeAccount.NodeAddress)
c.Assert(na.Bond.Equal(expectedBond), Equals, true)
c.Assert(na.Bond.Equal(expectedBond), Equals, true, Commentf("Bond: %d != %d", na.Bond.Uint64(), expectedBond.Uint64()))
vaultData, err = helper.keeper.GetVaultData(helper.ctx)
c.Assert(err, IsNil)
......
package thorchain
import (
"encoding/json"
"fmt"
"github.com/blang/semver"
......@@ -66,21 +65,9 @@ func (h SwapHandler) handle(ctx sdk.Context, msg MsgSwap, version semver.Version
}
}
func (h SwapHandler) addSwapEvent(ctx sdk.Context, swapEvt EventSwap, tx common.Tx, status EventStatus) error {
swapBytes, err := json.Marshal(swapEvt)
if err != nil {
return err
}
evt := NewEvent(swapEvt.Type(), ctx.BlockHeight(), tx, swapBytes, status)
if err := h.keeper.UpsertEvent(ctx, evt); err != nil {
return err
}
return nil
}
func (h SwapHandler) handleV1(ctx sdk.Context, msg MsgSwap, version semver.Version, constAccessor constants.ConstantValues) sdk.Result {
transactionFee := constAccessor.GetInt64Value(constants.TransactionFee)
amount, swapEvents, swapErr := swap(
amount, events, swapErr := swap(
ctx,
h.keeper,
msg.Tx,
......@@ -92,9 +79,9 @@ func (h SwapHandler) handleV1(ctx sdk.Context, msg MsgSwap, version semver.Versi
ctx.Logger().Error("fail to process swap message", "error", swapErr)
return swapErr.Result()
}
for _, item := range swapEvents {
if eventErr := h.addSwapEvent(ctx, item, msg.Tx, EventPending); eventErr != nil {
return sdk.ErrInternal(eventErr.Error()).Result()
for _, evt := range events {
if err := h.keeper.UpsertEvent(ctx, evt); err != nil {
return sdk.ErrInternal(err.Error()).Result()
}
}
......
......@@ -271,6 +271,12 @@ func updateEventStatus(ctx sdk.Context, keeper Keeper, eventID int64, txs common
if err != nil {
return fmt.Errorf("fail to get event: %w", err)
}
// if the event is already successful, don't append more transactions
if event.Status == EventSuccess {
return nil
}
ctx.Logger().Info(fmt.Sprintf("set event to %s,eventID (%d) , txs:%s", eventStatus, eventID, txs))
event.OutTxs = append(event.OutTxs, txs...)
if eventStatus == EventRefund {
......
package thorchain
import (
"encoding/json"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
......@@ -49,33 +50,40 @@ func swap(ctx sdk.Context,
target common.Asset,
destination common.Address,
tradeTarget sdk.Uint,
transactionFee sdk.Uint) (sdk.Uint, []EventSwap, sdk.Error) {
var swapEvents []EventSwap
transactionFee sdk.Uint) (sdk.Uint, []Event, sdk.Error) {
var swapEvents []Event
if err := validateMessage(tx, target, destination); err != nil {
return sdk.ZeroUint(), nil, sdk.NewError(DefaultCodespace, CodeValidationError, err.Error())
return sdk.ZeroUint(), swapEvents, sdk.NewError(DefaultCodespace, CodeValidationError, err.Error())
}
source := tx.Coins[0].Asset
if err := validatePools(ctx, keeper, source, target); err != nil {
return sdk.ZeroUint(), nil, err
return sdk.ZeroUint(), swapEvents, err
}
var swapEvt EventSwap
pools := make([]Pool, 0)
isDoubleSwap := !source.IsRune() && !target.IsRune()
if isDoubleSwap {
var err error
sourcePool, err := keeper.GetPool(ctx, source)
if err != nil {
return sdk.ZeroUint(), nil, sdk.ErrInternal(fmt.Errorf("fail to get %s pool : %w", source, err).Error())
return sdk.ZeroUint(), swapEvents, sdk.ErrInternal(fmt.Errorf("fail to get %s pool : %w", source, err).Error())
}
var swapErr sdk.Error
tx.Coins[0].Amount, sourcePool, swapEvt, swapErr = swapOne(ctx, keeper, tx, sourcePool, common.RuneAsset(), destination, tradeTarget, transactionFee)
var swapEvt Event
var amt sdk.Uint
amt, sourcePool, swapEvt, swapErr = swapOne(ctx, keeper, tx, sourcePool, common.RuneAsset(), destination, tradeTarget, transactionFee)
if swapErr != nil {
return sdk.ZeroUint(), nil, swapErr
return sdk.ZeroUint(), swapEvents, swapErr
}
pools = append(pools, sourcePool)
tx.Coins[0].Asset = common.RuneAsset()
tx.Coins = common.Coins{common.NewCoin(common.RuneAsset(), amt)}
tx.Gas = nil
// pre-populate the out transaction
swapEvt.Status = EventSuccess
swapEvt.OutTxs = common.Txs{common.NewTx(common.BlankTxID, tx.FromAddress, tx.ToAddress, tx.Coins, tx.Gas, tx.Memo)}
swapEvents = append(swapEvents, swapEvt)
}
......@@ -86,32 +94,34 @@ func swap(ctx sdk.Context,
}
pool, err := keeper.GetPool(ctx, asset)
if err != nil {
return sdk.ZeroUint(), nil, sdk.NewError(DefaultCodespace, CodeInvalidPoolStatus, err.Error())
return sdk.ZeroUint(), swapEvents, sdk.NewError(DefaultCodespace, CodeInvalidPoolStatus, err.Error())
}
assetAmount, pool, swapEvt, swapErr := swapOne(ctx, keeper, tx, pool, target, destination, tradeTarget, transactionFee)
if swapErr != nil {
return sdk.ZeroUint(), nil, swapErr
return sdk.ZeroUint(), swapEvents, swapErr
}
swapEvents = append(swapEvents, swapEvt)
pools = append(pools, pool)
if !tradeTarget.IsZero() && assetAmount.LT(tradeTarget) {
return sdk.ZeroUint(), nil, sdk.NewError(DefaultCodespace, CodeSwapFailTradeTarget, "emit asset %s less than price limit %s", assetAmount, tradeTarget)
return sdk.ZeroUint(), swapEvents, sdk.NewError(DefaultCodespace, CodeSwapFailTradeTarget, "emit asset %s less than price limit %s", assetAmount, tradeTarget)
}
if target.IsRune() {
if assetAmount.LTE(transactionFee) {
return sdk.ZeroUint(), nil, sdk.NewError(DefaultCodespace, CodeSwapFailNotEnoughFee, "output RUNE (%s) is not enough to pay transaction fee", assetAmount)
return sdk.ZeroUint(), swapEvents, sdk.NewError(DefaultCodespace, CodeSwapFailNotEnoughFee, "output RUNE (%s) is not enough to pay transaction fee", assetAmount)
}
}
// emit asset is zero
if assetAmount.IsZero() {
return sdk.ZeroUint(), nil, sdk.NewError(DefaultCodespace, CodeSwapFailZeroEmitAsset, "zero emit asset")
return sdk.ZeroUint(), swapEvents, sdk.NewError(DefaultCodespace, CodeSwapFailZeroEmitAsset, "zero emit asset")
}
// Update pools
for _, pool := range pools {
if err := keeper.SetPool(ctx, pool); err != nil {
return sdk.ZeroUint(), nil, sdk.NewError(DefaultCodespace, CodeSwapFail, err.Error())
return sdk.ZeroUint(), swapEvents, sdk.NewError(DefaultCodespace, CodeSwapFail, err.Error())
}
}
swapEvents = append(swapEvents, swapEvt)
return assetAmount, swapEvents, nil
}
......@@ -120,7 +130,7 @@ func swapOne(ctx sdk.Context,
target common.Asset,
destination common.Address,
tradeTarget sdk.Uint,
transactionFee sdk.Uint) (amt sdk.Uint, poolResult Pool, swapEvt EventSwap, err sdk.Error) {
transactionFee sdk.Uint) (amt sdk.Uint, poolResult Pool, evt Event, swapErr sdk.Error) {
source := tx.Coins[0].Asset
amount := tx.Coins[0].Amount
......@@ -135,11 +145,11 @@ func swapOne(ctx sdk.Context,
asset = target
if amount.LTE(transactionFee) {
// stop swap , because the output will not enough to pay for transaction fee
return sdk.ZeroUint(), Pool{}, swapEvt, sdk.NewError(DefaultCodespace, CodeSwapFailNotEnoughFee, "output RUNE (%s) is not enough to pay transaction fee", amount)
return sdk.ZeroUint(), Pool{}, evt, sdk.NewError(DefaultCodespace, CodeSwapFailNotEnoughFee, "output RUNE (%s) is not enough to pay transaction fee", amount)
}
}
swapEvt = NewEventSwap(
swapEvt := NewEventSwap(
asset,
tradeTarget,
sdk.ZeroUint(),
......@@ -149,17 +159,17 @@ func swapOne(ctx sdk.Context,
// Check if pool exists
if !keeper.PoolExist(ctx, asset) {
ctx.Logger().Debug(fmt.Sprintf("pool %s doesn't exist", asset))
return sdk.ZeroUint(), Pool{}, swapEvt, sdk.NewError(DefaultCodespace, CodeSwapFailPoolNotExist, "pool %s doesn't exist", asset)
return sdk.ZeroUint(), Pool{}, evt, sdk.NewError(DefaultCodespace, CodeSwapFailPoolNotExist, "pool %s doesn't exist", asset)
}
// Get our pool from the KVStore
pool, poolErr := keeper.GetPool(ctx, asset)
if poolErr != nil {
ctx.Logger().Error(fmt.Sprintf("fail to get pool(%s)", asset), "error", poolErr)
return sdk.ZeroUint(), Pool{}, swapEvt, sdk.NewError(DefaultCodespace, CodeSwapFailPoolNotExist, "pool %s doesn't exist", asset)
return sdk.ZeroUint(), Pool{}, evt, sdk.NewError(DefaultCodespace, CodeSwapFailPoolNotExist, "pool %s doesn't exist", asset)
}
if pool.Status != PoolEnabled {
return sdk.ZeroUint(), pool, swapEvt, sdk.NewError(DefaultCodespace, CodeInvalidPoolStatus, "pool %s is in %s status, can't swap", asset.String(), pool.Status)
return sdk.ZeroUint(), pool, evt, sdk.NewError(DefaultCodespace, CodeInvalidPoolStatus, "pool %s is in %s status, can't swap", asset.String(), pool.Status)
}
// Get our X, x, Y values
......@@ -174,10 +184,10 @@ func swapOne(ctx sdk.Context,
// check our X,x,Y values are valid
if x.IsZero() {
return sdk.ZeroUint(), pool, swapEvt, sdk.NewError(DefaultCodespace, CodeSwapFailInvalidAmount, "amount is invalid")
return sdk.ZeroUint(), pool, evt, sdk.NewError(DefaultCodespace, CodeSwapFailInvalidAmount, "amount is invalid")
}
if X.IsZero() || Y.IsZero() {
return sdk.ZeroUint(), pool, swapEvt, sdk.NewError(DefaultCodespace, CodeSwapFailInvalidBalance, "invalid balance")
return sdk.ZeroUint(), pool, evt, sdk.NewError(DefaultCodespace, CodeSwapFailInvalidBalance, "invalid balance")
}
liquidityFee = calcLiquidityFee(X, x, Y)
......@@ -191,12 +201,12 @@ func swapOne(ctx sdk.Context,
swapEvt.TradeSlip = tradeSlip
errLiquidityFee := keeper.AddToLiquidityFees(ctx, pool.Asset, liquidityFee)
if errLiquidityFee != nil {
return sdk.ZeroUint(), pool, swapEvt, sdk.ErrInternal(fmt.Errorf("fail to add liquidity: %w", err).Error())
return sdk.ZeroUint(), pool, evt, sdk.ErrInternal(fmt.Errorf("fail to add liquidity: %w", swapErr).Error())
}
// do THORNode have enough balance to swap?
if emitAssets.GT(Y) {
return sdk.ZeroUint(), pool, swapEvt, sdk.NewError(DefaultCodespace, CodeSwapFailNotEnoughBalance, "asset(%s) balance is %d, can't do swap", asset, Y)
return sdk.ZeroUint(), pool, evt, sdk.NewError(DefaultCodespace, CodeSwapFailNotEnoughBalance, "asset(%s) balance is %d, can't do swap", asset, Y)
}
ctx.Logger().Info(fmt.Sprintf("Pre-Pool: %sRune %sAsset", pool.BalanceRune, pool.BalanceAsset))
......@@ -210,7 +220,13 @@ func swapOne(ctx sdk.Context,
}
ctx.Logger().Info(fmt.Sprintf("Post-swap: %sRune %sAsset , user get:%s ", pool.BalanceRune, pool.BalanceAsset, emitAssets))
return emitAssets, pool, swapEvt, nil
swapBytes, err := json.Marshal(swapEvt)
if err != nil {
return sdk.ZeroUint(), pool, evt, sdk.NewError(DefaultCodespace, CodeSwapFailNotEnoughBalance, "err", err.Error())
}
evt = NewEvent(swapEvt.Type(), ctx.BlockHeight(), tx, swapBytes, EventPending)
return emitAssets, pool, evt, nil
}
// calculate the number of assets sent to the address (includes liquidity fee)
......
......@@ -33,6 +33,7 @@ func (s SwapSuite) TestSwap(c *C) {
returnAmount sdk.Uint
tradeTarget sdk.Uint
expectedErr sdk.Error
events []Event
}{
{
name: "empty-source",
......@@ -135,6 +136,9 @@ func (s SwapSuite) TestSwap(c *C) {
returnAmount: sdk.NewUint(2222222222),
tradeTarget: sdk.ZeroUint(),
expectedErr: nil,
events: []Event{
Event{ID: 0, Height: 0, Type: "swap", InTx: common.Tx{ID: "hash", Chain: "BNB", FromAddress: "tester", ToAddress: "don't know", Coins: common.Coins{common.NewCoin(common.RuneAsset(), sdk.NewUint(5000000000))}, Gas: common.Gas{common.NewCoin(common.BNBAsset, sdk.NewUint(37500))}}},
},
},
{
name: "swap-over-trade-sliplimit",
......@@ -159,6 +163,9 @@ func (s SwapSuite) TestSwap(c *C) {
returnAmount: sdk.NewUint(685871056),
tradeTarget: sdk.ZeroUint(),
expectedErr: nil,
events: []Event{
Event{ID: 0, Height: 0, Type: "swap", InTx: common.Tx{ID: "hash", Chain: "BNB", FromAddress: "tester", ToAddress: "don'tknow", Coins: common.Coins{common.NewCoin(common.RuneAsset(), sdk.NewUint(800000000))}, Gas: common.Gas{common.NewCoin(common.BNBAsset, sdk.NewUint(37500))}}},
},
},
{
name: "swap",
......@@ -171,6 +178,9 @@ func (s SwapSuite) TestSwap(c *C) {
returnAmount: sdk.NewUint(453514739),
tradeTarget: sdk.NewUint(453514738),
expectedErr: nil,
events: []Event{
Event{ID: 0, Height: 0, Type: "swap", InTx: common.Tx{ID: "hash", Chain: "BNB", FromAddress: "tester", ToAddress: "don'tknow", Coins: common.Coins{common.NewCoin(common.RuneAsset(), sdk.NewUint(500000000))}, Gas: common.Gas{common.NewCoin(common.BNBAsset, sdk.NewUint(37500))}}},
},
},
{
name: "double-swap",
......@@ -183,8 +193,13 @@ func (s SwapSuite) TestSwap(c *C) {
returnAmount: sdk.NewUint(415017809),
tradeTarget: sdk.NewUint(415017809),
expectedErr: nil,
events: []Event{
Event{ID: 0, Height: 0, Type: "swap", InTx: common.Tx{ID: "hash", Chain: "BNB", FromAddress: "tester", ToAddress: "don'tknow", Coins: common.Coins{common.NewCoin(common.BTCAsset, sdk.NewUint(5*common.One))}, Gas: common.Gas{common.NewCoin(common.BNBAsset, sdk.NewUint(37500))}}},
Event{ID: 0, Height: 0, Type: "swap", InTx: common.Tx{ID: "hash", Chain: "BNB", FromAddress: "tester", ToAddress: "don'tknow", Coins: common.Coins{common.NewCoin(common.RuneAsset(), sdk.NewUint(453514739))}, Gas: nil}},
},
},
}
for _, item := range inputs {
c.Logf("test name:%s", item.name)
tx := common.NewTx(
......@@ -198,10 +213,17 @@ func (s SwapSuite) TestSwap(c *C) {
"",
)
tx.Chain = common.BNBChain
amount, swapEvents, err := swap(ctx, poolStorage, tx, item.target, item.destination, item.tradeTarget, sdk.NewUint(1000_000))
amount, evts, err := swap(ctx, poolStorage, tx, item.target, item.destination, item.tradeTarget, sdk.NewUint(1000_000))
if item.expectedErr == nil {
c.Assert(err, IsNil)
c.Assert(len(swapEvents) > 0, Equals, true)
c.Assert(evts, HasLen, len(item.events))
for i := range evts {
c.Assert(item.events[i].ID, Equals, evts[i].ID)
c.Assert(item.events[i].Height, Equals, evts[i].Height)
c.Assert(item.events[i].Type, Equals, evts[i].Type)
c.Assert(item.events[i].InTx.Equals(evts[i].InTx), Equals, true, Commentf("%+v\n%+v", item.events[i].InTx, evts[i].InTx))
// TODO: test for price target, trade slip, and liquidity fee
}
} else {
c.Assert(err, NotNil, Commentf("Expected: %s, got nil", item.expectedErr.Error()))
c.Assert(err.Error(), Equals, item.expectedErr.Error())
......
......@@ -139,5 +139,5 @@ func (s TxOutStoreSuite) TestAddOutTxItemWithoutBFT(c *C) {
msgs, err := txOutStore.GetOutboundItems(w.ctx)
c.Assert(err, IsNil)
c.Assert(msgs, HasLen, 1)
c.Assert(msgs[0].Coin.Amount.Equal(sdk.NewUint(20*common.One)), Equals, true)
c.Assert(msgs[0].Coin.Amount.Equal(sdk.NewUint(19*common.One)), Equals, true)
}
......@@ -131,11 +131,9 @@ func (tos *TxOutStorageV1) prepareTxOutItem(ctx sdk.Context, toi *TxOutItem) (bo
}
// Deduct TransactionFee from TOI and add to Reserve
nodes, err := tos.keeper.TotalActiveNodeAccount(ctx)
minumNodesForBFT := tos.constAccessor.GetInt64Value(constants.MinimumNodesForBFT)
transactionFee := tos.constAccessor.GetInt64Value(constants.TransactionFee)
memo, _ := ParseMemo(toi.Memo) // ignore err
if int64(nodes) >= minumNodesForBFT && err == nil && !memo.IsType(txYggdrasilFund) && !memo.IsType(txYggdrasilReturn) && !memo.IsType(txMigrate) {
if err == nil && !memo.IsType(txYggdrasilFund) && !memo.IsType(txYggdrasilReturn) && !memo.IsType(txMigrate) {
var runeFee sdk.Uint
if toi.Coin.Asset.IsRune() {
if toi.Coin.Amount.LTE(sdk.NewUint(uint64(transactionFee))) {
......@@ -144,10 +142,12 @@ func (tos *TxOutStorageV1) prepareTxOutItem(ctx sdk.Context, toi *TxOutItem) (bo
runeFee = sdk.NewUint(uint64(transactionFee)) // Fee is the prescribed fee
}
toi.Coin.Amount = common.SafeSub(toi.Coin.Amount, runeFee)
/* Uncomment me. Temporally disabling adding rune fee to reserve to help test heimdall math
if err := tos.keeper.AddFeeToReserve(ctx, runeFee); err != nil {
// Add to reserve
ctx.Logger().Error("fail to add fee to reserve", "error", err)
}
*/
} else {
pool, err := tos.keeper.GetPool(ctx, toi.Coin.Asset) // Get pool
if err != nil {
......@@ -169,9 +169,11 @@ func (tos *TxOutStorageV1) prepareTxOutItem(ctx sdk.Context, toi *TxOutItem) (bo
if err := tos.keeper.SetPool(ctx, pool); err != nil { // Set Pool
return false, fmt.Errorf("fail to save pool: %w", err)
}
/* Uncomment me. Temporally disabling adding rune fee to reserve to help test heimdall math
if err := tos.keeper.AddFeeToReserve(ctx, runeFee); err != nil {
return false, fmt.Errorf("fail to add fee to reserve: %w", err)
}
*/
}
}
......