diff --git a/core/transaction/buy_coin_test.go b/core/transaction/buy_coin_test.go index 3a2bc732c..fc8701c1f 100644 --- a/core/transaction/buy_coin_test.go +++ b/core/transaction/buy_coin_test.go @@ -2,22 +2,28 @@ package transaction import ( "bytes" + "crypto/ecdsa" "fmt" "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" + "github.com/MinterTeam/minter-go-node/formula" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" "github.com/tendermint/go-amino" "github.com/tendermint/tm-db" "math/big" + "math/rand" "sync" "testing" + "time" ) var ( cdc = amino.NewCodec() + + rnd = rand.New(rand.NewSource(time.Now().Unix())) ) func getState() *state.State { @@ -32,7 +38,7 @@ func getState() *state.State { func getTestCoinSymbol() types.CoinSymbol { var coin types.CoinSymbol - copy(coin[:], []byte("TEST")) + copy(coin[:], "TEST") return coin } @@ -540,3 +546,569 @@ func TestBuyCoinReserveUnderflow(t *testing.T) { t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) } } + +func TestBuyCoinTxBaseToCustomBaseCommission(t *testing.T) { + // sell_coin: MNT + // buy_coin: TEST + // gas_coin: MNT + + coinToSell := types.StrToCoinSymbol("MNT") + coinToBuy := types.StrToCoinSymbol("TEST") + gasCoin := types.StrToCoinSymbol("MNT") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + toBuy := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + + tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + if buyCoinBalance.Cmp(toBuy) != 0 { + t.Fatalf("Buy coin balance is not correct") + } + + // check sold coins + commission + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, tx.CommissionInBaseCoin()) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, formula.CalculatePurchaseAmount(initialVolume, initialReserve, crr, toBuy)) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct") + } + + // check reserve and supply + coinData := cState.Coins.GetCoin(coinToBuy) + + estimatedReserve := big.NewInt(0).Set(initialReserve) + estimatedReserve.Add(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume, initialReserve, crr, toBuy)) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume) + estimatedSupply.Add(estimatedSupply, toBuy) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } +} + +func TestBuyCoinTxCustomToBaseBaseCommission(t *testing.T) { + // sell_coin: TEST + // buy_coin: MNT + // gas_coin: MNT + + coinToSell := types.StrToCoinSymbol("TEST") + coinToBuy := types.StrToCoinSymbol("MNT") + gasCoin := types.StrToCoinSymbol("MNT") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + initialGasBalance, _ := big.NewInt(0).SetString("100000000000000000", 10) + toBuy := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + + tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + if buyCoinBalance.Cmp(toBuy) != 0 { + t.Fatalf("Buy coin balance is not correct") + } + + // check sold coins + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, formula.CalculateSaleAmount(initialVolume, initialReserve, crr, toBuy)) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct") + } + + // check reserve and supply + coinData := cState.Coins.GetCoin(coinToSell) + + estimatedReserve := big.NewInt(0).Set(initialReserve) + estimatedReserve.Sub(estimatedReserve, toBuy) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve. Expected %s, got %s", estimatedReserve.String(), coinData.Reserve().String()) + } + + estimatedSupply := big.NewInt(0).Set(initialVolume) + estimatedSupply.Sub(estimatedSupply, formula.CalculateSaleAmount(initialVolume, initialReserve, crr, toBuy)) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } +} + +func TestBuyCoinTxCustomToCustomBaseCommission(t *testing.T) { + // sell_coin: TEST1 + // buy_coin: TEST2 + // gas_coin: MNT + + coinToSell := types.StrToCoinSymbol("TEST1") + coinToBuy := types.StrToCoinSymbol("TEST12") + gasCoin := types.StrToCoinSymbol("MNT") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + initialGasBalance, _ := big.NewInt(0).SetString("100000000000000000", 10) + + toBuy := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + + tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + if buyCoinBalance.Cmp(toBuy) != 0 { + t.Fatalf("Buy coin balance is not correct") + } + + // check sold coins + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + toSellBaseCoin := formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy) + toSell := formula.CalculateSaleAmount(initialVolume1, initialReserve1, crr1, toSellBaseCoin) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct. Expected %s, got %s", estimatedSellCoinBalance.String(), sellCoinBalance.String()) + } + + // check reserve and supply + coinData := cState.Coins.GetCoin(coinToSell) + + estimatedReserve := big.NewInt(0).Set(initialReserve1) + estimatedReserve.Sub(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy)) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve. Expected %s, got %s", estimatedReserve.String(), coinData.Reserve().String()) + } + + estimatedSupply := big.NewInt(0).Set(initialVolume1) + estimatedSupply.Sub(estimatedSupply, formula.CalculateSaleAmount(initialVolume1, initialReserve1, crr1, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy))) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } +} + +func TestBuyCoinTxBaseToCustomCustomCommission(t *testing.T) { + // sell_coin: MNT + // buy_coin: TEST + // gas_coin: TEST + + coinToSell := types.StrToCoinSymbol("MNT") + coinToBuy := types.StrToCoinSymbol("TEST") + gasCoin := types.StrToCoinSymbol("TEST") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + initialGasBalance := helpers.BipToPip(big.NewInt(1)) + toBuy := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + + tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + commission + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + estimatedBuyCoinBalance := big.NewInt(0).Set(toBuy) + estimatedBuyCoinBalance.Add(estimatedBuyCoinBalance, initialGasBalance) + toReserve := formula.CalculatePurchaseAmount(initialVolume, initialReserve, crr, toBuy) + commission := formula.CalculateSaleAmount(big.NewInt(0).Add(initialVolume, toBuy), big.NewInt(0).Add(initialReserve, toReserve), crr, tx.CommissionInBaseCoin()) + estimatedBuyCoinBalance.Sub(estimatedBuyCoinBalance, commission) + if buyCoinBalance.Cmp(estimatedBuyCoinBalance) != 0 { + t.Fatalf("Buy coin balance is not correct. Expected %s, got %s", estimatedBuyCoinBalance.String(), buyCoinBalance.String()) + } + + // check sold coins + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, formula.CalculatePurchaseAmount(initialVolume, initialReserve, crr, toBuy)) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct") + } + + // check reserve and supply + coinData := cState.Coins.GetCoin(coinToBuy) + + estimatedReserve := big.NewInt(0).Set(initialReserve) + estimatedReserve.Add(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume, initialReserve, crr, toBuy)) + estimatedReserve.Sub(estimatedReserve, tx.CommissionInBaseCoin()) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume) + estimatedSupply.Add(estimatedSupply, toBuy) + estimatedSupply.Sub(estimatedSupply, commission) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply. Expected %s, got %s", estimatedSupply.String(), coinData.Volume().String()) + } +} + +func TestBuyCoinTxCustomToBaseCustomCommission(t *testing.T) { + // sell_coin: TEST + // buy_coin: MNT + // gas_coin: TEST + + coinToSell := types.StrToCoinSymbol("TEST") + coinToBuy := types.StrToCoinSymbol("MNT") + gasCoin := types.StrToCoinSymbol("TEST") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + toBuy := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + + tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + if buyCoinBalance.Cmp(toBuy) != 0 { + t.Fatalf("Buy coin balance is not correct") + } + + // check sold coins + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + shouldGive := formula.CalculateSaleAmount(initialVolume, initialReserve, crr, big.NewInt(0).Add(toBuy, tx.CommissionInBaseCoin())) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, shouldGive) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct. Expected %s, got %s", estimatedSellCoinBalance.String(), sellCoinBalance.String()) + } + + // check reserve and supply + { + coinData := cState.Coins.GetCoin(coinToSell) + + estimatedReserve := big.NewInt(0).Set(initialReserve) + estimatedReserve.Sub(estimatedReserve, toBuy) + estimatedReserve.Sub(estimatedReserve, tx.CommissionInBaseCoin()) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve. Expected %s, got %s", estimatedReserve.String(), coinData.Reserve().String()) + } + + estimatedSupply := big.NewInt(0).Set(initialVolume) + estimatedSupply.Sub(estimatedSupply, formula.CalculateSaleAmount(initialVolume, initialReserve, crr, big.NewInt(0).Add(toBuy, tx.CommissionInBaseCoin()))) + //estimatedSupply.Sub(estimatedSupply, commission) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply. Expected %s, got %s", estimatedSupply.String(), coinData.Volume().String()) + } + } +} + +func TestBuyCoinTxCustomToCustomCustom1Commission(t *testing.T) { + // sell_coin: TEST1 + // buy_coin: TEST2 + // gas_coin: TEST1 + + coinToSell := types.StrToCoinSymbol("TEST1") + coinToBuy := types.StrToCoinSymbol("TEST2") + gasCoin := types.StrToCoinSymbol("TEST1") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + toBuy := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + + tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + if buyCoinBalance.Cmp(toBuy) != 0 { + t.Fatalf("Buy coin balance is not correct") + } + + // check sold coins + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + toSellBaseCoin := formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy) + toSell := formula.CalculateSaleAmount(initialVolume1, initialReserve1, crr1, toSellBaseCoin) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) + commission := formula.CalculateSaleAmount(big.NewInt(0).Sub(initialVolume1, toSell), big.NewInt(0).Sub(initialReserve1, toSellBaseCoin), crr1, tx.CommissionInBaseCoin()) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, commission) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct. Expected %s, got %s", estimatedSellCoinBalance.String(), sellCoinBalance.String()) + } + + // check reserve and supply + { + coinData := cState.Coins.GetCoin(coinToSell) + + estimatedReserve := big.NewInt(0).Set(initialReserve1) + estimatedReserve.Sub(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy)) + estimatedReserve.Sub(estimatedReserve, tx.CommissionInBaseCoin()) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve. Expected %s, got %s", estimatedReserve.String(), coinData.Reserve().String()) + } + + estimatedSupply := big.NewInt(0).Set(initialVolume1) + estimatedSupply.Sub(estimatedSupply, formula.CalculateSaleAmount(initialVolume1, initialReserve1, crr1, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy))) + estimatedSupply.Sub(estimatedSupply, commission) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } + } + + { + coinData := cState.Coins.GetCoin(coinToBuy) + + estimatedReserve := big.NewInt(0).Set(initialReserve2) + estimatedReserve.Add(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy)) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve. Expected %s, got %s", estimatedReserve.String(), coinData.Reserve().String()) + } + + estimatedSupply := big.NewInt(0).Set(initialVolume2) + estimatedSupply.Add(estimatedSupply, toBuy) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } + } +} + +func TestBuyCoinTxCustomToCustomCustom2Commission(t *testing.T) { + // sell_coin: TEST1 + // buy_coin: TEST2 + // gas_coin: TEST2 + + coinToSell := types.StrToCoinSymbol("TEST1") + coinToBuy := types.StrToCoinSymbol("TEST2") + gasCoin := types.StrToCoinSymbol("TEST2") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + initialGasBalance := helpers.BipToPip(big.NewInt(1)) + toBuy := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + + tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + buyCoinBalance.Sub(buyCoinBalance, initialGasBalance) + toReserve := formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy) + commission := formula.CalculateSaleAmount(big.NewInt(0).Add(initialVolume2, toBuy), big.NewInt(0).Add(initialReserve2, toReserve), crr2, tx.CommissionInBaseCoin()) + buyCoinBalance.Add(buyCoinBalance, commission) + if buyCoinBalance.Cmp(toBuy) != 0 { + t.Fatalf("Buy coin balance is not correct. Expected %s, got %s", toBuy.String(), buyCoinBalance.String()) + } + + // check sold coins + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + toSellBaseCoin := formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy) + toSell := formula.CalculateSaleAmount(initialVolume1, initialReserve1, crr1, toSellBaseCoin) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct. Expected %s, got %s", estimatedSellCoinBalance.String(), sellCoinBalance.String()) + } + + // check reserve and supply + { + coinData := cState.Coins.GetCoin(coinToSell) + + estimatedReserve := big.NewInt(0).Set(initialReserve1) + estimatedReserve.Sub(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy)) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve. Expected %s, got %s", estimatedReserve.String(), coinData.Reserve().String()) + } + + estimatedSupply := big.NewInt(0).Set(initialVolume1) + estimatedSupply.Sub(estimatedSupply, formula.CalculateSaleAmount(initialVolume1, initialReserve1, crr1, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy))) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } + } + + { + coinData := cState.Coins.GetCoin(coinToBuy) + + estimatedReserve := big.NewInt(0).Set(initialReserve2) + estimatedReserve.Add(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy)) + estimatedReserve.Sub(estimatedReserve, tx.CommissionInBaseCoin()) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve. Expected %s, got %s", estimatedReserve.String(), coinData.Reserve().String()) + } + + estimatedSupply := big.NewInt(0).Set(initialVolume2) + estimatedSupply.Add(estimatedSupply, toBuy) + estimatedSupply.Sub(estimatedSupply, commission) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } + } +} + +func createBuyCoinTx(sellCoin, buyCoin, gasCoin types.CoinSymbol, valueToBuy *big.Int, nonce uint64) *Transaction { + maxValToSell, _ := big.NewInt(0).SetString("100000000000000000000000000000", 10) + data := BuyCoinData{ + CoinToBuy: buyCoin, + ValueToBuy: valueToBuy, + CoinToSell: sellCoin, + MaximumValueToSell: maxValToSell, + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + panic(err) + } + + return &Transaction{ + Nonce: nonce, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: gasCoin, + Type: TypeBuyCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + + decodedData: data, + } +} + +func getAccount() (*ecdsa.PrivateKey, types.Address) { + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + return privateKey, addr +} + +func createTestCoinWithSymbol(stateDB *state.State, symbol types.CoinSymbol) (*big.Int, *big.Int, uint) { + volume := helpers.BipToPip(big.NewInt(100000)) + reserve := helpers.BipToPip(big.NewInt(100000)) + volume.Mul(volume, big.NewInt(int64(rnd.Intn(9))+1)) + reserve.Mul(reserve, big.NewInt(int64(rnd.Intn(9))+1)) + + crr := uint(10 + rnd.Intn(90)) + + stateDB.Coins.Create(symbol, "TEST COIN", volume, crr, reserve, big.NewInt(0).Mul(volume, big.NewInt(10))) + + return big.NewInt(0).Set(volume), big.NewInt(0).Set(reserve), crr +} diff --git a/core/transaction/sell_coin_test.go b/core/transaction/sell_coin_test.go index 5154a46f8..d170f7ecf 100644 --- a/core/transaction/sell_coin_test.go +++ b/core/transaction/sell_coin_test.go @@ -1,8 +1,10 @@ package transaction import ( + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" + "github.com/MinterTeam/minter-go-node/formula" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" "math/big" @@ -73,3 +75,568 @@ func TestSellCoinTx(t *testing.T) { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", getTestCoinSymbol(), targetTestBalance, testBalance) } } + +func TestSellCoinTxBaseToCustomBaseCommission(t *testing.T) { + // sell_coin: MNT + // buy_coin: TEST + // gas_coin: MNT + + coinToSell := types.StrToCoinSymbol("MNT") + coinToBuy := types.StrToCoinSymbol("TEST") + gasCoin := types.StrToCoinSymbol("MNT") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + toSell := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + + tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + estimatedBuyBalance := formula.CalculatePurchaseReturn(initialVolume, initialReserve, crr, toSell) + if buyCoinBalance.Cmp(estimatedBuyBalance) != 0 { + t.Fatalf("Buy coin balance is not correct. Expected %s, got %s", estimatedBuyBalance.String(), buyCoinBalance.String()) + } + + // check sold coins + commission + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, tx.CommissionInBaseCoin()) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct") + } + + // check reserve and supply + coinData := cState.Coins.GetCoin(coinToBuy) + + estimatedReserve := big.NewInt(0).Set(initialReserve) + estimatedReserve.Add(estimatedReserve, toSell) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume) + estimatedSupply.Add(estimatedSupply, formula.CalculatePurchaseReturn(initialVolume, initialReserve, crr, toSell)) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } +} + +func TestSellCoinTxCustomToBaseBaseCommission(t *testing.T) { + // sell_coin: TEST + // buy_coin: MNT + // gas_coin: MNT + + coinToSell := types.StrToCoinSymbol("TEST") + coinToBuy := types.StrToCoinSymbol("MNT") + gasCoin := types.StrToCoinSymbol("MNT") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + initialGasBalance := helpers.BipToPip(big.NewInt(1)) + toSell := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + + tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + commission + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + estimatedBuyBalance := formula.CalculateSaleReturn(initialVolume, initialReserve, crr, toSell) + estimatedBuyBalance.Add(estimatedBuyBalance, initialGasBalance) + estimatedBuyBalance.Sub(estimatedBuyBalance, tx.CommissionInBaseCoin()) + if buyCoinBalance.Cmp(estimatedBuyBalance) != 0 { + t.Fatalf("Buy coin balance is not correct. Expected %s, got %s", estimatedBuyBalance.String(), buyCoinBalance.String()) + } + + // check sold coins + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct") + } + + // check reserve and supply + coinData := cState.Coins.GetCoin(coinToSell) + + estimatedReserve := big.NewInt(0).Set(initialReserve) + estimatedReserve.Sub(estimatedReserve, formula.CalculateSaleReturn(initialVolume, initialReserve, crr, toSell)) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume) + estimatedSupply.Sub(estimatedSupply, toSell) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } +} + +func TestSellCoinTxCustomToCustomBaseCommission(t *testing.T) { + // sell_coin: TEST1 + // buy_coin: TEST2 + // gas_coin: MNT + + coinToSell := types.StrToCoinSymbol("TEST1") + coinToBuy := types.StrToCoinSymbol("TEST2") + gasCoin := types.StrToCoinSymbol("MNT") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + initialGasBalance := helpers.BipToPip(big.NewInt(1)) + toSell := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + + tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + estimatedBuyBalance := formula.CalculatePurchaseReturn(initialVolume2, initialReserve2, crr2, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) + if buyCoinBalance.Cmp(estimatedBuyBalance) != 0 { + t.Fatalf("Buy coin balance is not correct. Expected %s, got %s", estimatedBuyBalance.String(), buyCoinBalance.String()) + } + + // check sold coins + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct") + } + + // check reserve and supply + { + coinData := cState.Coins.GetCoin(coinToSell) + + estimatedReserve := big.NewInt(0).Set(initialReserve1) + estimatedReserve.Sub(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume1) + estimatedSupply.Sub(estimatedSupply, toSell) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } + } + + { + coinData := cState.Coins.GetCoin(coinToBuy) + + estimatedReserve := big.NewInt(0).Set(initialReserve2) + estimatedReserve.Add(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume2) + estimatedSupply.Add(estimatedSupply, formula.CalculatePurchaseReturn(initialVolume2, initialReserve2, crr2, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell))) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } + } +} + +func TestSellCoinTxBaseToCustomCustomCommission(t *testing.T) { + // sell_coin: MNT + // buy_coin: TEST + // gas_coin: TEST + + coinToSell := types.StrToCoinSymbol("MNT") + coinToBuy := types.StrToCoinSymbol("TEST") + gasCoin := types.StrToCoinSymbol("TEST") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + initialGasBalance := helpers.BipToPip(big.NewInt(1)) + toSell := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + + tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + commission + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + estimatedReturn := formula.CalculatePurchaseReturn(initialVolume, initialReserve, crr, toSell) + estimatedBuyBalance := big.NewInt(0).Set(estimatedReturn) + estimatedBuyBalance.Add(estimatedBuyBalance, initialGasBalance) + estimatedBuyBalance.Sub(estimatedBuyBalance, formula.CalculateSaleAmount(big.NewInt(0).Add(initialVolume, estimatedReturn), big.NewInt(0).Add(initialReserve, toSell), crr, tx.CommissionInBaseCoin())) + if buyCoinBalance.Cmp(estimatedBuyBalance) != 0 { + t.Fatalf("Buy coin balance is not correct. Expected %s, got %s", estimatedBuyBalance.String(), buyCoinBalance.String()) + } + + // check sold coins + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct") + } + + // check reserve and supply + coinData := cState.Coins.GetCoin(coinToBuy) + + estimatedReserve := big.NewInt(0).Set(initialReserve) + estimatedReserve.Add(estimatedReserve, toSell) + estimatedReserve.Sub(estimatedReserve, tx.CommissionInBaseCoin()) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume) + estimatedSupply.Add(estimatedSupply, formula.CalculatePurchaseReturn(initialVolume, initialReserve, crr, toSell)) + estimatedSupply.Sub(estimatedSupply, formula.CalculateSaleAmount(big.NewInt(0).Add(initialVolume, estimatedReturn), big.NewInt(0).Add(initialReserve, toSell), crr, tx.CommissionInBaseCoin())) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } +} + +func TestSellCoinTxCustomToBaseCustomCommission(t *testing.T) { + // sell_coin: TEST + // buy_coin: MNT + // gas_coin: TEST + + coinToSell := types.StrToCoinSymbol("TEST") + coinToBuy := types.StrToCoinSymbol("MNT") + gasCoin := types.StrToCoinSymbol("TEST") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + toSell := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + + tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + estimatedReturn := formula.CalculateSaleReturn(initialVolume, initialReserve, crr, toSell) + estimatedBuyBalance := big.NewInt(0).Set(estimatedReturn) + if buyCoinBalance.Cmp(estimatedBuyBalance) != 0 { + t.Fatalf("Buy coin balance is not correct. Expected %s, got %s", estimatedBuyBalance.String(), buyCoinBalance.String()) + } + + // check sold coins + commission + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, formula.CalculateSaleAmount(big.NewInt(0).Sub(initialVolume, toSell), big.NewInt(0).Sub(initialReserve, estimatedReturn), crr, tx.CommissionInBaseCoin())) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct. Expected %s, got %s", estimatedSellCoinBalance.String(), sellCoinBalance.String()) + } + + // check reserve and supply + coinData := cState.Coins.GetCoin(coinToSell) + + estimatedReserve := big.NewInt(0).Set(initialReserve) + estimatedReserve.Sub(estimatedReserve, estimatedReturn) + estimatedReserve.Sub(estimatedReserve, tx.CommissionInBaseCoin()) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume) + estimatedSupply.Sub(estimatedSupply, toSell) + estimatedSupply.Sub(estimatedSupply, formula.CalculateSaleAmount(big.NewInt(0).Sub(initialVolume, toSell), big.NewInt(0).Sub(initialReserve, estimatedReturn), crr, tx.CommissionInBaseCoin())) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } +} + +func TestSellCoinTxCustomToCustomCustom1Commission(t *testing.T) { + // sell_coin: TEST1 + // buy_coin: TEST2 + // gas_coin: TEST1 + + coinToSell := types.StrToCoinSymbol("TEST1") + coinToBuy := types.StrToCoinSymbol("TEST2") + gasCoin := types.StrToCoinSymbol("TEST1") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + toSell := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + + tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + bipReturn := formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell) + estimatedBuyBalance := formula.CalculatePurchaseReturn(initialVolume2, initialReserve2, crr2, bipReturn) + if buyCoinBalance.Cmp(estimatedBuyBalance) != 0 { + t.Fatalf("Buy coin balance is not correct. Expected %s, got %s", estimatedBuyBalance.String(), buyCoinBalance.String()) + } + + // check sold coins + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) + commission := formula.CalculateSaleAmount(big.NewInt(0).Sub(initialVolume1, toSell), big.NewInt(0).Sub(initialReserve1, bipReturn), crr1, tx.CommissionInBaseCoin()) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, commission) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct. Expected %s, got %s", estimatedSellCoinBalance.String(), sellCoinBalance.String()) + } + + // check reserve and supply + { + coinData := cState.Coins.GetCoin(coinToSell) + + estimatedReserve := big.NewInt(0).Set(initialReserve1) + estimatedReserve.Sub(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) + estimatedReserve.Sub(estimatedReserve, tx.CommissionInBaseCoin()) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume1) + estimatedSupply.Sub(estimatedSupply, toSell) + estimatedSupply.Sub(estimatedSupply, commission) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } + } + + { + coinData := cState.Coins.GetCoin(coinToBuy) + + estimatedReserve := big.NewInt(0).Set(initialReserve2) + estimatedReserve.Add(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume2) + estimatedSupply.Add(estimatedSupply, formula.CalculatePurchaseReturn(initialVolume2, initialReserve2, crr2, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell))) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } + } +} + +func TestSellCoinTxCustomToCustomCustom2Commission(t *testing.T) { + // sell_coin: TEST1 + // buy_coin: TEST2 + // gas_coin: TEST2 + + coinToSell := types.StrToCoinSymbol("TEST1") + coinToBuy := types.StrToCoinSymbol("TEST2") + gasCoin := types.StrToCoinSymbol("TEST2") + initialBalance := helpers.BipToPip(big.NewInt(10000000)) + initialGasBalance := helpers.BipToPip(big.NewInt(1)) + toSell := helpers.BipToPip(big.NewInt(100)) + + cState := getState() + initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + + privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + + tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + // check response + response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.OK { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + // check received coins + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + bipReturn := formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell) + estimatedReturn := formula.CalculatePurchaseReturn(initialVolume2, initialReserve2, crr2, bipReturn) + commission := formula.CalculateSaleAmount(big.NewInt(0).Add(initialVolume2, estimatedReturn), big.NewInt(0).Add(initialReserve2, bipReturn), crr2, tx.CommissionInBaseCoin()) + + estimatedBuyBalance := big.NewInt(0).Set(estimatedReturn) + estimatedBuyBalance.Sub(estimatedBuyBalance, commission) + estimatedBuyBalance.Add(estimatedBuyBalance, initialGasBalance) + if buyCoinBalance.Cmp(estimatedBuyBalance) != 0 { + t.Fatalf("Buy coin balance is not correct. Expected %s, got %s", estimatedBuyBalance.String(), buyCoinBalance.String()) + } + + // check sold coins + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) + estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) + if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { + t.Fatalf("Sell coin balance is not correct. Expected %s, got %s", estimatedSellCoinBalance.String(), sellCoinBalance.String()) + } + + // check reserve and supply + { + coinData := cState.Coins.GetCoin(coinToSell) + + estimatedReserve := big.NewInt(0).Set(initialReserve1) + estimatedReserve.Sub(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume1) + estimatedSupply.Sub(estimatedSupply, toSell) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } + } + + { + coinData := cState.Coins.GetCoin(coinToBuy) + + estimatedReserve := big.NewInt(0).Set(initialReserve2) + estimatedReserve.Add(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) + estimatedReserve.Sub(estimatedReserve, tx.CommissionInBaseCoin()) + if coinData.Reserve().Cmp(estimatedReserve) != 0 { + t.Fatalf("Wrong coin reserve") + } + + estimatedSupply := big.NewInt(0).Set(initialVolume2) + estimatedSupply.Add(estimatedSupply, formula.CalculatePurchaseReturn(initialVolume2, initialReserve2, crr2, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell))) + estimatedSupply.Sub(estimatedSupply, commission) + if coinData.Volume().Cmp(estimatedSupply) != 0 { + t.Fatalf("Wrong coin supply") + } + } +} + +func createSellCoinTx(sellCoin, buyCoin, gasCoin types.CoinSymbol, valueToSell *big.Int, nonce uint64) *Transaction { + data := SellCoinData{ + CoinToSell: sellCoin, + ValueToSell: valueToSell, + CoinToBuy: buyCoin, + MinimumValueToBuy: big.NewInt(0), + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + panic(err) + } + + return &Transaction{ + Nonce: nonce, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: gasCoin, + Type: TypeSellCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + + decodedData: data, + } +}