mirror of
https://github.com/muun/recovery.git
synced 2025-10-30 00:05:04 -04:00
1399 lines
49 KiB
Go
1399 lines
49 KiB
Go
package newop
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/muun/libwallet"
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
type testListener struct {
|
|
ch chan State
|
|
}
|
|
|
|
func newTestListener() *testListener {
|
|
return &testListener{ch: make(chan State, 10)}
|
|
}
|
|
|
|
func (t *testListener) OnStart(nextState *StartState) { t.ch <- nextState }
|
|
func (t *testListener) OnResolve(nextState *ResolveState) { t.ch <- nextState }
|
|
func (t *testListener) OnEnterAmount(nextState *EnterAmountState) { t.ch <- nextState }
|
|
func (t *testListener) OnEnterDescription(nextState *EnterDescriptionState) { t.ch <- nextState }
|
|
func (t *testListener) OnValidate(nextState *ValidateState) { t.ch <- nextState }
|
|
func (t *testListener) OnValidateLightning(nextState *ValidateLightningState) { t.ch <- nextState }
|
|
func (t *testListener) OnConfirm(nextState *ConfirmState) { t.ch <- nextState }
|
|
func (t *testListener) OnConfirmLightning(nextState *ConfirmLightningState) { t.ch <- nextState }
|
|
func (t *testListener) OnEditFee(nextState *EditFeeState) { t.ch <- nextState }
|
|
func (t *testListener) OnError(nextState *ErrorState) { t.ch <- nextState }
|
|
func (t *testListener) OnBalanceError(nextState *BalanceErrorState) { t.ch <- nextState }
|
|
func (t *testListener) OnAbort(nextState *AbortState) { t.ch <- nextState }
|
|
|
|
func (t *testListener) next() State {
|
|
return <-t.ch
|
|
}
|
|
|
|
var _ TransitionListener = &testListener{}
|
|
|
|
var testContext = createContext()
|
|
|
|
func createContext() *PaymentContext {
|
|
var context = &PaymentContext{
|
|
NextTransactionSize: &NextTransactionSize{},
|
|
ExchangeRateWindow: &ExchangeRateWindow{
|
|
rates: make(map[string]float64),
|
|
},
|
|
FeeWindow: &FeeWindow{},
|
|
PrimaryCurrency: "BTC",
|
|
MinFeeRateInSatsPerVByte: 1.0,
|
|
}
|
|
context.NextTransactionSize.AddSizeForAmount(&SizeForAmount{
|
|
AmountInSat: 100_000_000,
|
|
SizeInVByte: 240,
|
|
})
|
|
|
|
context.ExchangeRateWindow.AddRate("BTC", 1)
|
|
context.ExchangeRateWindow.AddRate("USD", 32_000)
|
|
context.ExchangeRateWindow.AddRate("ARS", 9_074_813.98)
|
|
|
|
context.FeeWindow.PutTargetedFees(1, 400.0)
|
|
context.FeeWindow.PutTargetedFees(15, 120.0)
|
|
context.FeeWindow.PutTargetedFees(90, 8.0)
|
|
|
|
return context
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestBarebonesOnChainFixedAmountFixedFee(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu?amount=0.1&description=foo", libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
resolveState.SetContext(testContext)
|
|
|
|
validateState := listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("bar")
|
|
|
|
confirmState := listener.next().(*ConfirmState)
|
|
|
|
if confirmState.Note != "bar" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.1 BTC" {
|
|
t.Fatalf("expected amount to match resolved URI, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.00096 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.10096 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestBarebonesOnChainFixedAmountFixedDescriptionFixedFee(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu?amount=0.1&message=foo", libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
resolveState.SetContext(testContext)
|
|
|
|
validateState := listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
confirmState := listener.next().(*ConfirmState)
|
|
|
|
if confirmState.Note != "foo" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.1 BTC" {
|
|
t.Fatalf("expected amount to match resolved URI, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.00096 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.10096 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestOnChainFixedAmountChangeFee(t *testing.T) {
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu?amount=0.1&description=foo", libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
resolveState.SetContext(testContext)
|
|
|
|
validateState := listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("bar")
|
|
|
|
confirmState := listener.next().(*ConfirmState)
|
|
|
|
if confirmState.FeeRateInSatsPerVByte != 400 {
|
|
t.Fatalf("expected initial fee rate to be 400, got %v", confirmState.FeeRateInSatsPerVByte)
|
|
}
|
|
|
|
newFeeRate := 15.0
|
|
|
|
confirmState.OpenFeeEditor()
|
|
|
|
editFeeState := listener.next().(*EditFeeState)
|
|
|
|
feeState, err := editFeeState.CalculateFee(newFeeRate)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if feeState.State != FeeStateFinalFee {
|
|
t.Fatalf("expected fee state to be FinalFee, got %v", feeState.State)
|
|
}
|
|
if feeState.Amount.InSat != 3600 {
|
|
t.Fatalf("expected fee amount to be 3600, got %v", feeState.Amount.InSat)
|
|
}
|
|
|
|
editFeeState.SetFeeRate(newFeeRate)
|
|
|
|
validateState = listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
confirmState = listener.next().(*ConfirmState)
|
|
|
|
if confirmState.Note != "bar" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.1 BTC" {
|
|
t.Fatalf("expected amount to match resolved URI, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.000036 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.100036 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestOnChainFixedAmountFeeNeedsChange(t *testing.T) {
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu?amount=0.9999&description=foo", libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
resolveState.SetContext(testContext)
|
|
|
|
validateState := listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("bar")
|
|
|
|
confirmState := listener.next().(*ConfirmState)
|
|
|
|
if confirmState.FeeRateInSatsPerVByte != 400 {
|
|
t.Fatalf("expected initial fee rate to be 400, got %v", confirmState.FeeRateInSatsPerVByte)
|
|
}
|
|
|
|
if !confirmState.FeeNeedsChange {
|
|
t.Fatalf("expected initial fee to be unpayable and need changing, got %v which is unpayable", confirmState.FeeRateInSatsPerVByte)
|
|
}
|
|
|
|
newFeeRate := 15.0
|
|
|
|
confirmState.OpenFeeEditor()
|
|
|
|
editFeeState := listener.next().(*EditFeeState)
|
|
|
|
feeState, err := editFeeState.CalculateFee(newFeeRate)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if feeState.State != FeeStateFinalFee {
|
|
t.Fatalf("expected fee state to be FinalFee, got %v", feeState.State)
|
|
}
|
|
if feeState.Amount.InSat != 3600 {
|
|
t.Fatalf("expected fee amount to be 3600, got %v", feeState.Amount.InSat)
|
|
}
|
|
|
|
editFeeState.SetFeeRate(newFeeRate)
|
|
|
|
validateState = listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
confirmState = listener.next().(*ConfirmState)
|
|
|
|
if confirmState.FeeNeedsChange {
|
|
t.Fatalf("expected fee to be payable, got %v which is not unpayable", confirmState.FeeRateInSatsPerVByte)
|
|
}
|
|
|
|
if confirmState.Note != "bar" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.9999 BTC" {
|
|
t.Fatalf("expected amount to match resolved URI, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.000036 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.999936 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestOnChainFixedAmountNoPossibleFee(t *testing.T) {
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu?amount=0.9999999&description=foo", libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
resolveState.SetContext(testContext)
|
|
|
|
validateState := listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
balanceErrorState := listener.next().(*BalanceErrorState)
|
|
|
|
if balanceErrorState.Error != OperationErrorUnpayable {
|
|
t.Fatalf("expected initial fee to be unpayable but got %s", balanceErrorState.Error)
|
|
}
|
|
|
|
if balanceErrorState.TotalAmount.String() != "1.0000023 BTC" {
|
|
t.Fatalf("expected total to match, got %v", balanceErrorState.TotalAmount)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestOnChainFixedAmountTooSmall(t *testing.T) {
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu?amount=0.0000004&description=foo", libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
resolveState.SetContext(testContext)
|
|
|
|
errorState := listener.next().(*ErrorState)
|
|
if errorState.Error != OperationErrorAmountTooSmall {
|
|
t.Fatalf("expected amount to be too small but got %s", errorState.Error)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestOnChainFixedAmountGreaterThanbalance(t *testing.T) {
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu?amount=2.0&description=foo", libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
resolveState.SetContext(testContext)
|
|
|
|
validateState := listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
balanceErrorState := listener.next().(*BalanceErrorState)
|
|
|
|
if balanceErrorState.Error != OperationErrorAmountGreaterThanBalance {
|
|
t.Fatalf("expected amount to be too small but got %s", balanceErrorState.Error)
|
|
}
|
|
|
|
if balanceErrorState.TotalAmount.String() != "2 BTC" {
|
|
t.Fatalf("expected total to match, got %v", balanceErrorState.TotalAmount)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestOnChainSendZeroFundsWithZeroBalance(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu", libwallet.Regtest())
|
|
|
|
context := createContext()
|
|
context.NextTransactionSize = &NextTransactionSize{}
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
resolveState.SetContext(context)
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(0), true)
|
|
|
|
validateState := listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
errorState := listener.next().(*ErrorState)
|
|
|
|
if errorState.Error != OperationErrorAmountTooSmall {
|
|
t.Fatalf("expected error to be amount too small but got %s", errorState.Error)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestOnChainTFFA(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu", libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
resolveState.SetContext(testContext)
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(100_000_000), true)
|
|
|
|
validateState := listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("bar")
|
|
if enterDescriptionState.Amount.InInputCurrency.String() != "0.99904 BTC" {
|
|
t.Fatalf("expected amount to match input, got %v", enterDescriptionState.Amount.InInputCurrency)
|
|
}
|
|
|
|
confirmState := listener.next().(*ConfirmState)
|
|
|
|
if confirmState.Note != "bar" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.99904 BTC" {
|
|
t.Fatalf("expected amount to match input, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.00096 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "1 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
|
|
confirmState.Back()
|
|
|
|
// check we preserve values correctly
|
|
enterDescriptionState = listener.next().(*EnterDescriptionState)
|
|
if enterDescriptionState.Note != "bar" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if enterDescriptionState.Amount.InInputCurrency.String() != "0.99904 BTC" {
|
|
t.Fatalf("expected amount to match input, got %v", enterDescriptionState.Amount.InInputCurrency)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestInvalidAmountEmitsInvalidAddress(t *testing.T) {
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu?amount=bananabanana", libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
resolveState.SetContext(testContext)
|
|
|
|
errorState := listener.next().(*ErrorState)
|
|
|
|
if errorState.Error != OperationErrorInvalidAddress {
|
|
t.Fatalf("expected error to be invalid address but got %s", errorState.Error)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestOnChainBack(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu", libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
resolveState.SetContext(testContext)
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(100_000_000), true)
|
|
|
|
validateState := listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("bar")
|
|
|
|
confirmState := listener.next().(*ConfirmState)
|
|
confirmState.Back()
|
|
|
|
enterDescriptionState = listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.Back()
|
|
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
// TODO when deleting this method impl (deprecated) rm lines below up until the call to ChangeCurrencyWithAmount
|
|
enterAmountState.ChangeCurrency("USD")
|
|
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
enterAmountState.Back()
|
|
|
|
abortState := listener.next().(*AbortState)
|
|
if abortState.update != UpdateAll {
|
|
t.Fatalf("expected normal/full update , got %v", abortState.update)
|
|
}
|
|
abortState.Cancel()
|
|
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
if enterAmountState.update != UpdateEmpty {
|
|
t.Fatalf("expected empty update, got %v", enterAmountState.update)
|
|
}
|
|
enterAmountState.Back()
|
|
|
|
abortState = listener.next().(*AbortState)
|
|
if abortState.update != UpdateAll {
|
|
t.Fatalf("expected normal/full update , got %v", abortState.update)
|
|
}
|
|
abortState.Cancel()
|
|
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
enterAmountState.ChangeCurrencyWithAmount("USD", NewMonetaryAmountFromSatoshis(1_000_000))
|
|
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
enterAmountState.Back()
|
|
|
|
abortState = listener.next().(*AbortState)
|
|
if abortState.update != UpdateAll {
|
|
t.Fatalf("expected normal/full update , got %v", abortState.update)
|
|
}
|
|
abortState.Cancel()
|
|
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
if enterAmountState.update != UpdateEmpty {
|
|
t.Fatalf("expected empty update, got %v", enterAmountState.update)
|
|
}
|
|
enterAmountState.Back()
|
|
|
|
abortState = listener.next().(*AbortState)
|
|
if abortState.update != UpdateAll {
|
|
t.Fatalf("expected normal/full update , got %v", abortState.update)
|
|
}
|
|
abortState.Cancel()
|
|
|
|
// One more, just for the giggles
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
enterAmountState.Back()
|
|
|
|
_ = listener.next().(*AbortState)
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestOnChainChangeCurrency(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu", libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
resolveState.SetContext(testContext)
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
inputAmountCurrency := enterAmountState.Amount.InInputCurrency.Currency
|
|
balanceCurrency := enterAmountState.TotalBalance.InInputCurrency.Currency
|
|
if inputAmountCurrency != balanceCurrency {
|
|
t.Fatalf("expected amount currency (%v) to match balance currency (%v)", inputAmountCurrency, balanceCurrency)
|
|
}
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(100_000_000), true)
|
|
|
|
validateState := listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
if enterDescriptionState.Amount.InInputCurrency.String() != "0.99904 BTC" {
|
|
t.Fatalf("expected amount to match input, got %v", enterDescriptionState.Amount.InInputCurrency)
|
|
}
|
|
|
|
enterDescriptionState.Back()
|
|
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
// TODO when deleting this method impl (deprecated) rm lines below up until the call to ChangeCurrencyWithAmount
|
|
enterAmountState.ChangeCurrency("USD")
|
|
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
if enterAmountState.update != UpdateInPlace {
|
|
t.Fatalf("expected UpdateInPlace, got '%v'", enterAmountState.update)
|
|
}
|
|
if enterAmountState.Amount.InSat != 100_000_000 {
|
|
t.Fatalf("expected amount to match 100_000_000, got '%v'", enterAmountState.Amount.InSat)
|
|
}
|
|
if enterAmountState.Amount.InInputCurrency.String() != "32000 USD" {
|
|
t.Fatalf("expected amount to match 32000 USD, got '%v'", enterAmountState.Amount.InInputCurrency.String())
|
|
}
|
|
if enterAmountState.Amount.InPrimaryCurrency.String() != "1 BTC" {
|
|
t.Fatalf("expected amount to match 1 BTC, got '%v'", enterAmountState.Amount.InPrimaryCurrency.String())
|
|
}
|
|
|
|
enterAmountState.ChangeCurrency("BTC")
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
if enterAmountState.update != UpdateInPlace {
|
|
t.Fatalf("expected UpdateInPlace, got '%v'", enterAmountState.update)
|
|
}
|
|
if enterAmountState.Amount.InSat != 100_000_000 {
|
|
t.Fatalf("expected amount to match 100_000_000, got '%v'", enterAmountState.Amount.InSat)
|
|
}
|
|
if enterAmountState.Amount.InInputCurrency.String() != "1 BTC" {
|
|
t.Fatalf("expected amount to match 1 BTC, got '%v'", enterAmountState.Amount.InInputCurrency.String())
|
|
}
|
|
if enterAmountState.Amount.InPrimaryCurrency.String() != "1 BTC" {
|
|
t.Fatalf("expected amount to match 1 BTC, got '%v'", enterAmountState.Amount.InPrimaryCurrency.String())
|
|
}
|
|
|
|
enterAmountState.ChangeCurrencyWithAmount("USD", NewMonetaryAmountFromSatoshis(1_000_000))
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
if enterAmountState.update != UpdateInPlace {
|
|
t.Fatalf("expected UpdateInPlace, got '%v'", enterAmountState.update)
|
|
}
|
|
if enterAmountState.Amount.InSat != 1_000_000 {
|
|
t.Fatalf("expected amount to match 1_000_000, got '%v'", enterAmountState.Amount.InSat)
|
|
}
|
|
if enterAmountState.Amount.InInputCurrency.String() != "320 USD" {
|
|
t.Fatalf("expected amount to match 320 USD, got '%v'", enterAmountState.Amount.InInputCurrency.String())
|
|
}
|
|
if enterAmountState.Amount.InPrimaryCurrency.String() != "0.01 BTC" {
|
|
t.Fatalf("expected amount to match 0.01 BTC, got '%v'", enterAmountState.Amount.InPrimaryCurrency.String())
|
|
}
|
|
|
|
enterAmountState.ChangeCurrencyWithAmount("BTC", enterAmountState.Amount.InInputCurrency)
|
|
enterAmountState = listener.next().(*EnterAmountState)
|
|
if enterAmountState.update != UpdateInPlace {
|
|
t.Fatalf("expected UpdateInPlace, got '%v'", enterAmountState.update)
|
|
}
|
|
if enterAmountState.Amount.InSat != 1_000_000 {
|
|
t.Fatalf("expected amount to match 1_000_000, got '%v'", enterAmountState.Amount.InSat)
|
|
}
|
|
if enterAmountState.Amount.InInputCurrency.String() != "0.01 BTC" {
|
|
t.Fatalf("expected amount to match 0.01 BTC, got '%v'", enterAmountState.Amount.InInputCurrency.String())
|
|
}
|
|
if enterAmountState.Amount.InPrimaryCurrency.String() != "0.01 BTC" {
|
|
t.Fatalf("expected amount to match 0.01 BTC, got '%v'", enterAmountState.Amount.InPrimaryCurrency.String())
|
|
}
|
|
|
|
enterDescriptionState.EnterDescription("bar")
|
|
confirmState := listener.next().(*ConfirmState)
|
|
|
|
if confirmState.Note != "bar" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.99904 BTC" {
|
|
t.Fatalf("expected amount to match input, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.00096 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "1 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
|
|
confirmState.Back()
|
|
|
|
// check we preserve values correctly
|
|
enterDescriptionState = listener.next().(*EnterDescriptionState)
|
|
if enterDescriptionState.Note != "bar" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if enterDescriptionState.Amount.InInputCurrency.String() != "0.99904 BTC" {
|
|
t.Fatalf("expected amount to match input, got %v", enterDescriptionState.Amount.InInputCurrency)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestLightningSendZeroFunds(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt1ps3l7zlpp5ngv7sl4wrjalma9navd0w9956pu0tcqwrltcnnzz83eeyk4rszxqdqqcqzpgrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqgqqqqqqqlgqqqqqqgq9qrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqsqqqqqqqlgqqqqqqgq9qsp5luyagw4mtcq735je8ldukhlkg063cxzycjhpz2x2hjfq2mgk5xns9qyyssq7ydzdwyl7yr6ldpzqjjspmgrevw4lxt4jwfy3cxm7we20wveqq8p8khjxuq9u3v953e7t9r8ysfzx5r874vu3nd7w5yx5eqfxu0tevspgxr607", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.NextTransactionSize = &NextTransactionSize{}
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
BestRouteFees: []*BestRouteFees{
|
|
{
|
|
MaxCapacity: 100_000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 1_000,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &FundingOutputPolicies{
|
|
MaximumDebtInSat: 0,
|
|
PotentialCollectInSat: 0,
|
|
MaxAmountInSatFor0Conf: 25_000,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629485164, 0))
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(0), false)
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
errorState := listener.next().(*ErrorState)
|
|
|
|
if errorState.Error != OperationErrorAmountTooSmall {
|
|
t.Fatalf("expected error to be amount too small but got %s", errorState.Error)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestLightningSendZeroFundsTFFA(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt1ps3l7zlpp5ngv7sl4wrjalma9navd0w9956pu0tcqwrltcnnzz83eeyk4rszxqdqqcqzpgrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqgqqqqqqqlgqqqqqqgq9qrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqsqqqqqqqlgqqqqqqgq9qsp5luyagw4mtcq735je8ldukhlkg063cxzycjhpz2x2hjfq2mgk5xns9qyyssq7ydzdwyl7yr6ldpzqjjspmgrevw4lxt4jwfy3cxm7we20wveqq8p8khjxuq9u3v953e7t9r8ysfzx5r874vu3nd7w5yx5eqfxu0tevspgxr607", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.NextTransactionSize = &NextTransactionSize{}
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
BestRouteFees: []*BestRouteFees{
|
|
{
|
|
MaxCapacity: 100_000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 1_000,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &FundingOutputPolicies{
|
|
MaximumDebtInSat: 0,
|
|
PotentialCollectInSat: 0,
|
|
MaxAmountInSatFor0Conf: 25_000,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629485164, 0))
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(0), true)
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
errorState := listener.next().(*ErrorState)
|
|
|
|
if errorState.Error != OperationErrorAmountTooSmall {
|
|
t.Fatalf("expected error to be amount too small but got %s", errorState.Error)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestLightningSendNegativeFunds(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt1ps3l7zlpp5ngv7sl4wrjalma9navd0w9956pu0tcqwrltcnnzz83eeyk4rszxqdqqcqzpgrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqgqqqqqqqlgqqqqqqgq9qrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqsqqqqqqqlgqqqqqqgq9qsp5luyagw4mtcq735je8ldukhlkg063cxzycjhpz2x2hjfq2mgk5xns9qyyssq7ydzdwyl7yr6ldpzqjjspmgrevw4lxt4jwfy3cxm7we20wveqq8p8khjxuq9u3v953e7t9r8ysfzx5r874vu3nd7w5yx5eqfxu0tevspgxr607", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.NextTransactionSize = &NextTransactionSize{}
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
BestRouteFees: []*BestRouteFees{
|
|
{
|
|
MaxCapacity: 100_000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 1_000,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &FundingOutputPolicies{
|
|
MaximumDebtInSat: 0,
|
|
PotentialCollectInSat: 0,
|
|
MaxAmountInSatFor0Conf: 25_000,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629485164, 0))
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(-10), false)
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
errorState := listener.next().(*ErrorState)
|
|
|
|
if errorState.Error != OperationErrorAmountTooSmall {
|
|
t.Fatalf("expected error to be amount too small but got %s", errorState.Error)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestLightningSendNegativeFundsWithTFFA(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt1ps3l7zlpp5ngv7sl4wrjalma9navd0w9956pu0tcqwrltcnnzz83eeyk4rszxqdqqcqzpgrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqgqqqqqqqlgqqqqqqgq9qrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqsqqqqqqqlgqqqqqqgq9qsp5luyagw4mtcq735je8ldukhlkg063cxzycjhpz2x2hjfq2mgk5xns9qyyssq7ydzdwyl7yr6ldpzqjjspmgrevw4lxt4jwfy3cxm7we20wveqq8p8khjxuq9u3v953e7t9r8ysfzx5r874vu3nd7w5yx5eqfxu0tevspgxr607", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.NextTransactionSize = &NextTransactionSize{}
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
BestRouteFees: []*BestRouteFees{
|
|
{
|
|
MaxCapacity: 100_000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 1_000,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &FundingOutputPolicies{
|
|
MaximumDebtInSat: 0,
|
|
PotentialCollectInSat: 0,
|
|
MaxAmountInSatFor0Conf: 25_000,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629485164, 0))
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(-10), true)
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
errorState := listener.next().(*ErrorState)
|
|
|
|
if errorState.Error != OperationErrorAmountTooSmall {
|
|
t.Fatalf("expected error to be amount too small but got %s", errorState.Error)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestLightningExpiredInvoice(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt100u1ps3kdrgpp5klwrzs0u63sqnca8elqu86p98swxycw3fgjtmeddm2ljd7ymrlwsdqqcqzpgsp5zs02vngwrywtqhwygu44wp464lgjtqyc5h76vae073064p72znas9qyyssqz2263utx4n7r7n85s9wg3ma2zmg3xtg46nj3e6nnr6g67tnj6jwn7urvx5qukhqjzmcnuc4t7uqlxhftqwq4hxha3ests23fcmt5evqpazdmg2", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
resolveState.SetContext(testContext)
|
|
|
|
errorState := listener.next().(*ErrorState)
|
|
|
|
if errorState.Error != OperationErrorInvoiceExpired {
|
|
t.Fatalf("expected error to match, got '%v'", errorState.Error)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestLightningInvoiceWithAmount(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt100u1ps3l5eepp5njeddrlmsg9cd2a4v508mqucz7tdge90vvp4f5n23gh7kthnjjdqdqqcqzpgrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqgqqqqqqqlgqqqqqqgq9qsp52qtk90062t5mha837ulm77vf04ph4kaxerm8xugjdkp9gk6d8yqs9qyyssqw09stp3vy33dfjc6vcrdfmf58trg5pte6efph9pj9gwlg0w7anhz6aelv0p3r9qj6vrjjw9jyj6s9tjujec2fm9k8ag3yvgvwszswxsqhl6equ", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
Fees: &SwapFees{
|
|
RoutingFeeInSat: 0,
|
|
DebtType: DebtTypeNone,
|
|
DebtAmountInSat: 0,
|
|
OutputAmountInSat: 10000,
|
|
OutputPaddingInSat: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629475653, 0))
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("foo")
|
|
|
|
confirmState := listener.next().(*ConfirmLightningState)
|
|
|
|
if confirmState.Note != "foo" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.0001 BTC" {
|
|
t.Fatalf("expected amount to match resolved URI, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.0000192 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.0001192 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestLightningWithAmountBack(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt100u1ps3l5eepp5njeddrlmsg9cd2a4v508mqucz7tdge90vvp4f5n23gh7kthnjjdqdqqcqzpgrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqgqqqqqqqlgqqqqqqgq9qsp52qtk90062t5mha837ulm77vf04ph4kaxerm8xugjdkp9gk6d8yqs9qyyssqw09stp3vy33dfjc6vcrdfmf58trg5pte6efph9pj9gwlg0w7anhz6aelv0p3r9qj6vrjjw9jyj6s9tjujec2fm9k8ag3yvgvwszswxsqhl6equ", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
Fees: &SwapFees{
|
|
RoutingFeeInSat: 0,
|
|
DebtType: DebtTypeNone,
|
|
DebtAmountInSat: 0,
|
|
OutputAmountInSat: 10000,
|
|
OutputPaddingInSat: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629475653, 0))
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("foo")
|
|
|
|
confirmState := listener.next().(*ConfirmLightningState)
|
|
|
|
confirmState.Back()
|
|
enterDescriptionState = listener.next().(*EnterDescriptionState)
|
|
if enterDescriptionState.Note != "foo" {
|
|
t.Fatalf("expected note to match input, got '%v'", enterDescriptionState.Note)
|
|
}
|
|
|
|
enterDescriptionState.EnterDescription("bar")
|
|
confirmState = listener.next().(*ConfirmLightningState)
|
|
|
|
if confirmState.Note != "bar" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.0001 BTC" {
|
|
t.Fatalf("expected amount to match resolved URI, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.0000192 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.0001192 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestLightningInvoiceWithAmountAndDescription(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt100u1psjg8k6pp5n895ngj22v4dczwd8jrvvq76qvur2642y29m6x2faq0rgle2zwwsdq2vehk7cnpwgcqzpgsp5x9yeys2j294q402ewq2kfcas6wn63mk5q86ehe79plljzfwhr69s9qyyssq4exlg7ly068zc8dfh6ls5r69x0pmvdy9la70hw2vqwz9p2g4p5fyxr0hlkzrfnkmlx3kjrlecedatk96zuzs8a3cj48qg7vne6zp5ygpgzwd2x", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
Fees: &SwapFees{
|
|
RoutingFeeInSat: 0,
|
|
DebtType: DebtTypeNone,
|
|
DebtAmountInSat: 0,
|
|
OutputAmountInSat: 10000,
|
|
OutputPaddingInSat: 0,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629475653, 0))
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
confirmState := listener.next().(*ConfirmLightningState)
|
|
|
|
if confirmState.Note != "foobar" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.0001 BTC" {
|
|
t.Fatalf("expected amount to match resolved URI, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.0000192 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.0001192 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestLightningAmountlessInvoice(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt1ps3l7zlpp5ngv7sl4wrjalma9navd0w9956pu0tcqwrltcnnzz83eeyk4rszxqdqqcqzpgrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqgqqqqqqqlgqqqqqqgq9qrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqsqqqqqqqlgqqqqqqgq9qsp5luyagw4mtcq735je8ldukhlkg063cxzycjhpz2x2hjfq2mgk5xns9qyyssq7ydzdwyl7yr6ldpzqjjspmgrevw4lxt4jwfy3cxm7we20wveqq8p8khjxuq9u3v953e7t9r8ysfzx5r874vu3nd7w5yx5eqfxu0tevspgxr607", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
BestRouteFees: []*BestRouteFees{
|
|
{
|
|
MaxCapacity: 100_000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 1_000,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &FundingOutputPolicies{
|
|
MaximumDebtInSat: 0,
|
|
PotentialCollectInSat: 0,
|
|
MaxAmountInSatFor0Conf: 25_000,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629485164, 0))
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(10_000), false)
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("foo")
|
|
|
|
confirmState := listener.next().(*ConfirmLightningState)
|
|
|
|
if confirmState.Note != "foo" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.0001 BTC" {
|
|
t.Fatalf("expected amount to match resolved URI, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.0000292 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.0001292 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
if confirmState.SwapInfo.IsOneConf {
|
|
t.Fatalf("expected swap to be 0 conf")
|
|
}
|
|
|
|
// Test a bug where the one conf flag was set after back
|
|
confirmState.Back()
|
|
enterDescriptionState = listener.next().(*EnterDescriptionState)
|
|
if enterDescriptionState.SwapInfo.IsOneConf {
|
|
t.Fatalf("expected swap to be 0 conf")
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestInvoiceOneConf(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt1ps3l7zlpp5ngv7sl4wrjalma9navd0w9956pu0tcqwrltcnnzz83eeyk4rszxqdqqcqzpgrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqgqqqqqqqlgqqqqqqgq9qrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqsqqqqqqqlgqqqqqqgq9qsp5luyagw4mtcq735je8ldukhlkg063cxzycjhpz2x2hjfq2mgk5xns9qyyssq7ydzdwyl7yr6ldpzqjjspmgrevw4lxt4jwfy3cxm7we20wveqq8p8khjxuq9u3v953e7t9r8ysfzx5r874vu3nd7w5yx5eqfxu0tevspgxr607", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
BestRouteFees: []*BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 1000,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &FundingOutputPolicies{
|
|
MaximumDebtInSat: 0,
|
|
PotentialCollectInSat: 0,
|
|
MaxAmountInSatFor0Conf: 0,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629485164, 0))
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(10000), false)
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("foo")
|
|
if !enterDescriptionState.SwapInfo.IsOneConf {
|
|
t.Fatalf("expected swap to be 1 conf")
|
|
}
|
|
|
|
confirmState := listener.next().(*ConfirmLightningState)
|
|
|
|
if confirmState.Note != "foo" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.0001 BTC" {
|
|
t.Fatalf("expected amount to match resolved URI, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.00097 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.00107 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
if !confirmState.SwapInfo.IsOneConf {
|
|
t.Fatalf("expected swap to be 1 conf")
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestAmountConversion(t *testing.T) {
|
|
|
|
// This test repros a bug where we had:
|
|
// * Primary currency BTC
|
|
// * Fiat input
|
|
// Then, for amount/total the amount in sat and in primary currency differed
|
|
// in 1 sat.
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt1ps3l7zlpp5ngv7sl4wrjalma9navd0w9956pu0tcqwrltcnnzz83eeyk4rszxqdqqcqzpgrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqgqqqqqqqlgqqqqqqgq9qrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqsqqqqqqqlgqqqqqqgq9qsp5luyagw4mtcq735je8ldukhlkg063cxzycjhpz2x2hjfq2mgk5xns9qyyssq7ydzdwyl7yr6ldpzqjjspmgrevw4lxt4jwfy3cxm7we20wveqq8p8khjxuq9u3v953e7t9r8ysfzx5r874vu3nd7w5yx5eqfxu0tevspgxr607", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
BestRouteFees: []*BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 1000,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &FundingOutputPolicies{
|
|
MaximumDebtInSat: 0,
|
|
PotentialCollectInSat: 0,
|
|
MaxAmountInSatFor0Conf: 0,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629485164, 0))
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromFiat("2500", "ARS"), false)
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("foo")
|
|
|
|
confirmState := listener.next().(*ConfirmLightningState)
|
|
btcToSats := decimal.NewFromInt(100_000_000)
|
|
|
|
inPrimary := confirmState.Amount.InPrimaryCurrency.Value
|
|
// This emulates the conversions app make (and the Right Way™ to do it)
|
|
primaryInSats := inPrimary.Mul(btcToSats).RoundBank(0).IntPart()
|
|
if primaryInSats != confirmState.Amount.InSat {
|
|
t.Fatalf(
|
|
"expected amount in primary to match sats: %v != %v",
|
|
primaryInSats,
|
|
confirmState.Amount.InSat,
|
|
)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestInvoiceUnpayable(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt1ps3l7zlpp5ngv7sl4wrjalma9navd0w9956pu0tcqwrltcnnzz83eeyk4rszxqdqqcqzpgrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqgqqqqqqqlgqqqqqqgq9qrzjq2eawnq2ywdmcpe56nk02tfamgfmsn0acp0zcn8z8cr0djkgpslr5qqpkgqqqqsqqqqqqqlgqqqqqqgq9qsp5luyagw4mtcq735je8ldukhlkg063cxzycjhpz2x2hjfq2mgk5xns9qyyssq7ydzdwyl7yr6ldpzqjjspmgrevw4lxt4jwfy3cxm7we20wveqq8p8khjxuq9u3v953e7t9r8ysfzx5r874vu3nd7w5yx5eqfxu0tevspgxr607", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
BestRouteFees: []*BestRouteFees{
|
|
{
|
|
MaxCapacity: 100000,
|
|
FeeProportionalMillionth: 1,
|
|
FeeBase: 1000,
|
|
},
|
|
},
|
|
FundingOutputPolicies: &FundingOutputPolicies{
|
|
MaximumDebtInSat: 0,
|
|
PotentialCollectInSat: 0,
|
|
MaxAmountInSatFor0Conf: 0,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629485164, 0))
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(99_999_999), false)
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
errorState := listener.next().(*BalanceErrorState)
|
|
if errorState.Error != OperationErrorUnpayable {
|
|
t.Fatalf("Expected error to be OperationErrorUnpayable: %v", errorState.Error)
|
|
}
|
|
if errorState.Balance.String() != "1 BTC" {
|
|
t.Fatalf("Expected balance to be 1 BTC: %v", errorState.Balance.String())
|
|
}
|
|
if errorState.TotalAmount.String() != "1.00097098 BTC" {
|
|
t.Fatalf("Expected total amount to be 1 BTC: %v", errorState.TotalAmount.String())
|
|
}
|
|
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestInvoiceLend(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
invoice, err := libwallet.ParseInvoice("lnbcrt1u1ps5ma8app59ujxjvtj8x34fyd7u7tghuq44dphjth8nqmkzeklg882y9ghjmvqdqqcqzpgsp5stzjqktxfh02dfz8tucfnh6rl3z87ctl2dumr40elhmrskhx5zlq9qyyssqnp4uhukcgxx6l0p5elppz5xc7a97n0hxvfm6lgr6ze06wqc2dnx95pet3vlalc9rqz20lu45y8sqg3n6fm6tqsftvzqp4l2zsrs0ndgpk2fyv5", libwallet.Regtest())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
startState.ResolveInvoice(invoice, libwallet.Regtest())
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
|
|
context := createContext()
|
|
context.SubmarineSwap = &SubmarineSwap{
|
|
Fees: &SwapFees{
|
|
RoutingFeeInSat: 0,
|
|
DebtType: DebtTypeLend,
|
|
DebtAmountInSat: 100,
|
|
OutputAmountInSat: 546,
|
|
OutputPaddingInSat: 446,
|
|
ConfirmationsNeeded: 0,
|
|
},
|
|
}
|
|
|
|
resolveState.setContextWithTime(context, time.Unix(1629475653, 0))
|
|
|
|
validateState := listener.next().(*ValidateLightningState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("foo")
|
|
|
|
confirmState := listener.next().(*ConfirmLightningState)
|
|
|
|
if confirmState.Note != "foo" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.000001 BTC" {
|
|
t.Fatalf("expected amount to match resolved URI, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.000001 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
if confirmState.SwapInfo.SwapFees.DebtType != DebtTypeLend {
|
|
t.Fatalf("Expected debt type to be lend: %v", confirmState.SwapInfo.SwapFees.DebtType)
|
|
}
|
|
if confirmState.SwapInfo.SwapFees.DebtAmountInSat != 100 {
|
|
t.Fatalf("Expected debt amount to be 100 sats: %v", confirmState.SwapInfo.SwapFees.DebtAmountInSat)
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestAmountInfo_Mutating(t *testing.T) {
|
|
amountInfo := &AmountInfo{
|
|
TakeFeeFromAmount: false,
|
|
FeeRateInSatsPerVByte: 0,
|
|
}
|
|
|
|
mutated := amountInfo.mutating(func(info *AmountInfo) {
|
|
info.TakeFeeFromAmount = true
|
|
})
|
|
|
|
if amountInfo.TakeFeeFromAmount {
|
|
t.Fatalf("Expected original to stay the same")
|
|
}
|
|
if !mutated.TakeFeeFromAmount {
|
|
t.Fatalf("Mutated should be mutated")
|
|
}
|
|
}
|
|
|
|
//goland:noinspection GoUnhandledErrorResult
|
|
func TestOnChainTFFAWithDebtFeeNeedsChangeBecauseOutputAmountLowerThanDust(t *testing.T) {
|
|
|
|
listener := newTestListener()
|
|
startState := NewOperationFlow(listener)
|
|
|
|
startState.Resolve("bitcoin:bcrt1qj35fkq34xend9w0ssthn432vl9pxxsuy0epzlu", libwallet.Regtest())
|
|
|
|
context := createContext()
|
|
|
|
nts := &NextTransactionSize{}
|
|
nts.AddSizeForAmount(&SizeForAmount{
|
|
AmountInSat: 5338,
|
|
SizeInVByte: 172,
|
|
})
|
|
nts.ExpectedDebtInSat = 4353
|
|
context.NextTransactionSize = nts
|
|
|
|
context.FeeWindow.PutTargetedFees(100, 1.0)
|
|
|
|
resolveState := listener.next().(*ResolveState)
|
|
resolveState.SetContext(context)
|
|
|
|
enterAmountState := listener.next().(*EnterAmountState)
|
|
enterAmountState.EnterAmount(NewMonetaryAmountFromSatoshis(985), true)
|
|
|
|
validateState := listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
enterDescriptionState := listener.next().(*EnterDescriptionState)
|
|
enterDescriptionState.EnterDescription("bar")
|
|
|
|
// Amount is not payable with fastes/highest fee, but it is with min fee (1 sat/vbyte)
|
|
if enterDescriptionState.Amount.InInputCurrency.String() != "0 BTC" {
|
|
t.Fatalf("expected amount to match input, got %v", enterDescriptionState.Amount.InInputCurrency)
|
|
}
|
|
|
|
confirmState := listener.next().(*ConfirmState)
|
|
|
|
if confirmState.Note != "bar" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0 BTC" {
|
|
t.Fatalf("expected amount to match input, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.000688 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.000688 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
if confirmState.FeeNeedsChange != true {
|
|
t.Fatalf("expected feedsNeedsChange to be true, got %v", confirmState.FeeNeedsChange)
|
|
}
|
|
|
|
confirmState.OpenFeeEditor()
|
|
editFeeState := listener.next().(*EditFeeState)
|
|
|
|
editFeeState.SetFeeRate(1)
|
|
|
|
validateState = listener.next().(*ValidateState)
|
|
validateState.Continue()
|
|
|
|
confirmState = listener.next().(*ConfirmState)
|
|
|
|
if confirmState.Note != "bar" {
|
|
t.Fatalf("expected note to match input, got '%v'", confirmState.Note)
|
|
}
|
|
if confirmState.Amount.InInputCurrency.String() != "0.00000813 BTC" {
|
|
t.Fatalf("expected amount to match input, got %v", confirmState.Amount.InInputCurrency)
|
|
}
|
|
if confirmState.Fee.InInputCurrency.String() != "0.00000172 BTC" {
|
|
t.Fatalf("expected fee to match, got %v", confirmState.Fee.InInputCurrency)
|
|
}
|
|
if confirmState.Total.InInputCurrency.String() != "0.00000985 BTC" {
|
|
t.Fatalf("expected total to match, got %v", confirmState.Total.InInputCurrency)
|
|
}
|
|
if confirmState.FeeNeedsChange != false {
|
|
t.Fatalf("expected feedsNeedsChange to be false, got %v", confirmState.FeeNeedsChange)
|
|
}
|
|
}
|