...
 
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 {
......
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,41 @@ 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
// Here we use a tradeTarget of 0 because the target is for the next swap asset in a double swap
amt, sourcePool, swapEvt, swapErr = swapOne(ctx, keeper, tx, sourcePool, common.RuneAsset(), destination, sdk.ZeroUint(), 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 +95,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 +131,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 +146,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 +160,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 +185,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 +202,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 +221,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())
......