From c36001a7638a1a33509d19c58623518de6cbefd6 Mon Sep 17 00:00:00 2001 From: Stanislas Polu Date: Fri, 13 Jan 2017 11:23:46 -0800 Subject: [PATCH 1/5] Cosmetic changes to list and pay --- cli/command/list.go | 7 ------- cli/command/pay.go | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/cli/command/list.go b/cli/command/list.go index 71ec2c8..3268c33 100644 --- a/cli/command/list.go +++ b/cli/command/list.go @@ -171,7 +171,6 @@ func (c *List) ExecuteAssets( data := [][][2]string{} for _, a := range assets { data = append(data, [][2]string{ - [2]string{"ID", a.ID}, [2]string{"Created", fmt.Sprintf("%d", a.Created)}, [2]string{"Asset", a.Name}, }) @@ -207,8 +206,6 @@ func (c *List) ExecuteBalances( data := [][][2]string{} for _, b := range balances { data = append(data, [][2]string{ - [2]string{"ID", b.ID}, - [2]string{"Created", fmt.Sprintf("%d", b.Created)}, [2]string{"Asset", b.Asset}, [2]string{"Holder", b.Holder}, [2]string{"Value", b.Value.String()}, @@ -240,8 +237,6 @@ func (c *List) ExecuteTrustlines( data := [][][2]string{} for _, o := range cOffers { data = append(data, [][2]string{ - [2]string{"ID", o.ID}, - [2]string{"Created", fmt.Sprintf("%d", o.Created)}, [2]string{"Pair", o.Pair}, [2]string{"Price", o.Price}, [2]string{"Amount", o.Amount.String()}, @@ -259,8 +254,6 @@ func (c *List) ExecuteTrustlines( data = [][][2]string{} for _, o := range pOffers { data = append(data, [][2]string{ - [2]string{"ID", o.ID}, - [2]string{"Created", fmt.Sprintf("%d", o.Created)}, [2]string{"Pair", o.Pair}, [2]string{"Price", o.Price}, [2]string{"Amount", o.Amount.String()}, diff --git a/cli/command/pay.go b/cli/command/pay.go index 1d8e9fc..6796f1e 100644 --- a/cli/command/pay.go +++ b/cli/command/pay.go @@ -186,7 +186,7 @@ func (c *Pay) Execute( out.Valuf("%s\n", c.Amount.String()) out.Normf(" Path : ") if len(c.Path) == 0 { - out.Normf("(empty)") + out.Normf("(empty)\n") } else { for j, o := range c.Path { if j > 0 { @@ -234,7 +234,7 @@ func (c *Pay) Execute( out.Valuf("%s %s\n", c.QuoteAsset, c.Amount.String()) out.Normf(" Path : ") if len(candidate.Path) == 0 { - out.Normf("(empty)") + out.Normf("(empty)\n") } else { for j, o := range candidate.Path { if j > 0 { From 5dcef29b0008cd2c186c6bbad0106e2ab2a3cab2 Mon Sep 17 00:00:00 2001 From: Stanislas Polu Date: Fri, 13 Jan 2017 11:32:52 -0800 Subject: [PATCH 2/5] Clean up and show final settled transaction --- cli/command/pay.go | 28 +++++++++++++++++++++++++++ mint/async/task/expire_transaction.go | 2 +- mint/model/transaction.go | 3 --- mint/protocol.go | 1 - 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/cli/command/pay.go b/cli/command/pay.go index 6796f1e..b30c700 100644 --- a/cli/command/pay.go +++ b/cli/command/pay.go @@ -270,6 +270,34 @@ func (c *Pay) Execute( return errors.Trace(err) } + out.Boldf("Transaction settled:\n") + out.Normf(" ID : ") + out.Valuf("%s\n", tx.ID) + out.Normf(" Created : ") + out.Valuf("%d\n", tx.Created) + out.Normf(" Owner : ") + out.Valuf("%s\n", tx.Owner) + out.Normf(" Pair : ") + out.Valuf("%s\n", tx.Pair) + out.Normf(" Amount : ") + out.Valuf("%s\n", tx.Amount.String()) + out.Normf(" Destination : ") + out.Valuf("%s\n", tx.Destination) + out.Normf(" Path : ") + if len(tx.Path) == 0 { + out.Normf("(empty)\n") + } else { + for j, o := range tx.Path { + if j > 0 { + out.Normf("\n ") + } + out.Valuf(o) + } + out.Normf("\n") + } + out.Normf(" Status : ") + out.Valuf("%s\n", tx.Status) + return nil } diff --git a/mint/async/task/expire_transaction.go b/mint/async/task/expire_transaction.go index ccab415..c9721f3 100644 --- a/mint/async/task/expire_transaction.go +++ b/mint/async/task/expire_transaction.go @@ -65,7 +65,7 @@ func (t *ExpireTransaction) DeadlineForRetry( retry uint, ) time.Time { expiry := time.Duration(mint.TransactionExpiryMs) * time.Millisecond - return t.Created().Add(expiry + time.Duration(retry+1)*expiry) + return t.Created().Add(expiry + time.Duration(retry)*expiry) } // Execute idempotently runs the task to completion or errors. diff --git a/mint/model/transaction.go b/mint/model/transaction.go index dcbb3ac..5e2169e 100644 --- a/mint/model/transaction.go +++ b/mint/model/transaction.go @@ -33,7 +33,6 @@ type Transaction struct { Status mint.TxStatus - Expiry time.Time Lock string Secret *string } @@ -56,7 +55,6 @@ func NewTransactionResource( Amount: (*big.Int)(&transaction.Amount), Destination: transaction.Destination, Path: []string(transaction.Path), - Expiry: transaction.Expiry.UnixNano() / mint.TimeResolutionNs, Status: transaction.Status, Lock: transaction.Lock, Operations: []mint.OperationResource{}, @@ -203,7 +201,6 @@ func (t *Transaction) ID() string { func (t *Transaction) Save( ctx context.Context, ) error { - t.Expiry = t.Expiry.UTC() ext := db.Ext(ctx, "mint") _, err := sqlx.NamedExec(ext, ` UPDATE transactions diff --git a/mint/protocol.go b/mint/protocol.go index 3281c3a..5cf528e 100644 --- a/mint/protocol.go +++ b/mint/protocol.go @@ -137,7 +137,6 @@ type TransactionResource struct { Path []string `json:"path"` Status TxStatus `json:"status"` - Expiry int64 `json:"expiry"` Lock string `json:"lock"` Secret *string `json:"secret"` From 8bc13bd8254f6cb3183e535c3fa379f4534e0d91 Mon Sep 17 00:00:00 2001 From: Stanislas Polu Date: Fri, 13 Jan 2017 11:45:07 -0800 Subject: [PATCH 3/5] Prevent offer on pair of the same asset --- TODO | 5 ++++- mint/endpoint/create_offer.go | 7 +++++++ mint/test/functional/create_offer_test.go | 23 +++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index dccf979..2c97697 100644 --- a/TODO +++ b/TODO @@ -12,7 +12,7 @@ # preview [ ] fix mint - [ ] error on offer with same currency + [ ] paying yourself with a currency you trust does not decrease the balance [ ] finish site [ ] blog post draft [ ] documentation @@ -22,6 +22,7 @@ [ ] close (offer) command [ ] pay with path of length 2 + [ ] list transactions (canonical or destination) [x] pay command [x] check existence of quote asset on trust [x] check credentials @@ -45,11 +46,13 @@ # mint [~] list endpoint + [ ] list (asset) transactions [ ] list asset operations [x] list asset offers (order book) [x] list asset balances [x] list balances [x] list assets + [x] error on offer with same currency [x] allow cancellation without plan computation (use offer path, move CheckCanShouldCancel to plan)) [x] reintroduce transaction expiry on top of safe cancelation [x] propagate cancellation on syncrhonous failure diff --git a/mint/endpoint/create_offer.go b/mint/endpoint/create_offer.go index 11a82e8..aeb5e03 100644 --- a/mint/endpoint/create_offer.go +++ b/mint/endpoint/create_offer.go @@ -87,6 +87,13 @@ func (e *CreateOffer) Validate( )) } + if e.Pair[0].Name == e.Pair[1].Name { + return errors.Trace(errors.NewUserErrorf(nil, + 400, "pair_invalid", + "You cannot create an offer with the same base and quote asset.", + )) + } + // Validate price. basePrice, quotePrice, err := ValidatePrice(ctx, r.PostFormValue("price")) if err != nil { diff --git a/mint/test/functional/create_offer_test.go b/mint/test/functional/create_offer_test.go index 22bf021..afa0285 100644 --- a/mint/test/functional/create_offer_test.go +++ b/mint/test/functional/create_offer_test.go @@ -122,3 +122,26 @@ func TestCreateOfferWithInexistantAsset( assert.Equal(t, 400, status) assert.Equal(t, "asset_not_found", e.ErrCode) } + +func TestCreateOfferWithSameAsset( + t *testing.T, +) { + t.Parallel() + m, u, _ := setupCreateOffer(t) + defer tearDownCreateOffer(t, m) + + status, raw := u[0].Post(t, + fmt.Sprintf("/offers"), + url.Values{ + "pair": {fmt.Sprintf("%s[USD.2]/%s[USD.2]", u[0].Address, u[0].Address)}, + "price": {"1/1"}, + "amount": {"100"}, + }) + + var e errors.ConcreteUserError + err := raw.Extract("error", &e) + assert.Nil(t, err) + + assert.Equal(t, 400, status) + assert.Equal(t, "pair_invalid", e.ErrCode) +} From 990d5ac8c276b3117b2ae17fcb67b038717abb36 Mon Sep 17 00:00:00 2001 From: Stanislas Polu Date: Fri, 13 Jan 2017 11:48:57 -0800 Subject: [PATCH 4/5] Cleanup protocol file --- mint/async.go | 16 ++++++++++++++++ mint/protocol.go | 15 --------------- 2 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 mint/async.go diff --git a/mint/async.go b/mint/async.go new file mode 100644 index 0000000..b5a57e6 --- /dev/null +++ b/mint/async.go @@ -0,0 +1,16 @@ +package mint + +// TkName represents a task name. +type TkName string + +// TkStatus represents a task status. +type TkStatus string + +const ( + // TkStPending new or have been retried less than the task max retries. + TkStPending TkStatus = "pending" + // TkStSucceeded successfully executed once. + TkStSucceeded TkStatus = "succeeded" + // TkStFailed retried more than max retries with no success. + TkStFailed TkStatus = "failed" +) diff --git a/mint/protocol.go b/mint/protocol.go index 5cf528e..862d22d 100644 --- a/mint/protocol.go +++ b/mint/protocol.go @@ -143,18 +143,3 @@ type TransactionResource struct { Operations []OperationResource `json:"operations"` Crossings []CrossingResource `json:"crossings"` } - -// TkName represents a task name. -type TkName string - -// TkStatus represents a task status. -type TkStatus string - -const ( - // TkStPending new or have been retried less than the task max retries. - TkStPending TkStatus = "pending" - // TkStSucceeded successfully executed once. - TkStSucceeded TkStatus = "succeeded" - // TkStFailed retried more than max retries with no success. - TkStFailed TkStatus = "failed" -) From 995435031bbbe4757855e7369f058dc7ea5a2751 Mon Sep 17 00:00:00 2001 From: Stanislas Polu Date: Fri, 13 Jan 2017 13:26:52 -0800 Subject: [PATCH 5/5] Test paying oneself with self offer and remote asset --- TODO | 4 +- .../functional/create_transaction_test.go | 93 ++++++++++++++++ .../functional/settle_transaction_test.go | 103 ++++++++++++++++++ 3 files changed, 198 insertions(+), 2 deletions(-) diff --git a/TODO b/TODO index 2c97697..10323fa 100644 --- a/TODO +++ b/TODO @@ -11,9 +11,8 @@ # preview - [ ] fix mint - [ ] paying yourself with a currency you trust does not decrease the balance [ ] finish site + [ ] cut 0.0.2-pre [ ] blog post draft [ ] documentation [ ] send r? @@ -52,6 +51,7 @@ [x] list asset balances [x] list balances [x] list assets + [x] paying yourself with a currency you trust does not decrease the balance (expeted, tested) [x] error on offer with same currency [x] allow cancellation without plan computation (use offer path, move CheckCanShouldCancel to plan)) [x] reintroduce transaction expiry on top of safe cancelation diff --git a/mint/test/functional/create_transaction_test.go b/mint/test/functional/create_transaction_test.go index 30b10ee..03abf77 100644 --- a/mint/test/functional/create_transaction_test.go +++ b/mint/test/functional/create_transaction_test.go @@ -633,3 +633,96 @@ func TestCreateTransactionWithNoOfferAndRemoteBaseAsset( assert.Nil(t, err) assert.Equal(t, big.NewInt(5), (*big.Int)(&balance.Value)) } + +func TestCreateTransactionUsingOfferToPayOneself( + t *testing.T, +) { + t.Parallel() + m, u, a, o := setupCreateTransaction(t) + defer tearDownCreateTransaction(t, m) + + // Credit u[1] on m[0] + status, raw := u[0].Post(t, + fmt.Sprintf("/transactions"), + url.Values{ + "pair": {fmt.Sprintf("%s/%s", a[0].Name, a[0].Name)}, + "amount": {"10"}, + "destination": {u[1].Address}, + "path[]": {}, + }) + + assert.Equal(t, 201, status) + + var tx mint.TransactionResource + err := raw.Extract("transaction", &tx) + assert.Nil(t, err) + + status, raw = u[0].Post(t, + fmt.Sprintf("/transactions/%s/settle", tx.ID), + url.Values{}) + + assert.Equal(t, 200, status) + + err = raw.Extract("transaction", &tx) + assert.Nil(t, err) + + // Pay u[1] (himself) using his balance at m[0] + status, raw = u[1].Post(t, + fmt.Sprintf("/transactions"), + url.Values{ + "pair": {fmt.Sprintf("%s/%s", a[0].Name, a[1].Name)}, + "amount": {"5"}, + "destination": {u[1].Address}, + "path[]": { + o[1].ID, + }, + }) + + assert.Equal(t, 201, status) + + var tx1 mint.TransactionResource + err = raw.Extract("transaction", &tx1) + assert.Nil(t, err) + + assert.Regexp(t, mint.IDRegexp, tx1.ID) + assert.Equal(t, mint.TxStReserved, tx1.Status) + assert.Equal(t, 1, len(tx1.Operations)) + assert.Equal(t, 1, len(tx1.Crossings)) + + assert.Equal(t, mint.TxStReserved, tx1.Operations[0].Status) + assert.Equal(t, big.NewInt(5), tx1.Operations[0].Amount) + assert.Equal(t, u[1].Address, tx1.Operations[0].Destination) + assert.Equal(t, u[1].Address, tx1.Operations[0].Source) + + assert.Equal(t, mint.TxStReserved, tx1.Crossings[0].Status) + assert.Equal(t, u[1].Address, tx1.Crossings[0].Owner) + assert.Equal(t, o[1].ID, tx1.Crossings[0].Offer) + assert.Equal(t, big.NewInt(5), tx1.Crossings[0].Amount) + + // Check transaction on m[0]. + status, raw = u[0].Get(t, fmt.Sprintf("/transactions/%s", tx1.ID)) + + assert.Equal(t, 200, status) + + var tx0 mint.TransactionResource + err = raw.Extract("transaction", &tx0) + assert.Nil(t, err) + + assert.Regexp(t, mint.IDRegexp, tx0.ID) + assert.Equal(t, mint.TxStReserved, tx0.Status) + assert.Equal(t, 1, len(tx0.Operations)) + assert.Equal(t, 0, len(tx0.Crossings)) + + assert.Equal(t, mint.TxStReserved, tx0.Operations[0].Status) + assert.Equal(t, big.NewInt(5), tx0.Operations[0].Amount) + assert.Equal(t, u[1].Address, tx0.Operations[0].Destination) + assert.Equal(t, u[1].Address, tx0.Operations[0].Source) + + // Check balance on m[1] + balance, err := model.LoadCanonicalBalanceByAssetHolder(m[0].Ctx, + a[0].Name, u[1].Address) + assert.Nil(t, err) + // The balance should be 5 because you've reserved 5 for yourself (it will + // go back at 10 post settlement). + assert.Equal(t, big.NewInt(5), (*big.Int)(&balance.Value)) +} diff --git a/mint/test/functional/settle_transaction_test.go b/mint/test/functional/settle_transaction_test.go index 47939ae..54f4a5f 100644 --- a/mint/test/functional/settle_transaction_test.go +++ b/mint/test/functional/settle_transaction_test.go @@ -425,3 +425,106 @@ func TestSettleTransactionWithNoOfferAndRemoteBaseAsset( assert.Nil(t, err) assert.Equal(t, big.NewInt(5), (*big.Int)(&balance.Value)) } + +func TestSettleTransactionUsingOfferToPayOneself( + t *testing.T, +) { + t.Parallel() + m, u, a, o := setupSettleTransaction(t) + defer tearDownSettleTransaction(t, m) + + // Credit u[1] on m[0] + status, raw := u[0].Post(t, + fmt.Sprintf("/transactions"), + url.Values{ + "pair": {fmt.Sprintf("%s/%s", a[0].Name, a[0].Name)}, + "amount": {"10"}, + "destination": {u[1].Address}, + "path[]": {}, + }) + + assert.Equal(t, 201, status) + + var tx mint.TransactionResource + err := raw.Extract("transaction", &tx) + assert.Nil(t, err) + + status, raw = u[0].Post(t, + fmt.Sprintf("/transactions/%s/settle", tx.ID), + url.Values{}) + + assert.Equal(t, 200, status) + + err = raw.Extract("transaction", &tx) + assert.Nil(t, err) + + // Pay u[1] (himself) using his balance at m[0] + status, raw = u[1].Post(t, + fmt.Sprintf("/transactions"), + url.Values{ + "pair": {fmt.Sprintf("%s/%s", a[0].Name, a[1].Name)}, + "amount": {"5"}, + "destination": {u[1].Address}, + "path[]": { + o[1].ID, + }, + }) + + assert.Equal(t, 201, status) + + var tx1 mint.TransactionResource + err = raw.Extract("transaction", &tx1) + assert.Nil(t, err) + + status, raw = u[1].Post(t, + fmt.Sprintf("/transactions/%s/settle", tx1.ID), + url.Values{}) + + assert.Equal(t, 200, status) + + err = raw.Extract("transaction", &tx1) + assert.Nil(t, err) + + assert.Regexp(t, mint.IDRegexp, tx1.ID) + assert.Equal(t, mint.TxStSettled, tx1.Status) + assert.Equal(t, 1, len(tx1.Operations)) + assert.Equal(t, 1, len(tx1.Crossings)) + + assert.Equal(t, mint.TxStSettled, tx1.Operations[0].Status) + assert.Equal(t, big.NewInt(5), tx1.Operations[0].Amount) + assert.Equal(t, u[1].Address, tx1.Operations[0].Destination) + assert.Equal(t, u[1].Address, tx1.Operations[0].Source) + + assert.Equal(t, mint.TxStSettled, tx1.Crossings[0].Status) + assert.Equal(t, u[1].Address, tx1.Crossings[0].Owner) + assert.Equal(t, o[1].ID, tx1.Crossings[0].Offer) + assert.Equal(t, big.NewInt(5), tx1.Crossings[0].Amount) + + // Check transaction on m[0]. + status, raw = u[0].Get(t, fmt.Sprintf("/transactions/%s", tx1.ID)) + + assert.Equal(t, 200, status) + + var tx0 mint.TransactionResource + err = raw.Extract("transaction", &tx0) + assert.Nil(t, err) + + assert.Regexp(t, mint.IDRegexp, tx0.ID) + assert.Equal(t, mint.TxStSettled, tx0.Status) + assert.Equal(t, 1, len(tx0.Operations)) + assert.Equal(t, 0, len(tx0.Crossings)) + + assert.Equal(t, mint.TxStSettled, tx0.Operations[0].Status) + assert.Equal(t, big.NewInt(5), tx0.Operations[0].Amount) + assert.Equal(t, u[1].Address, tx0.Operations[0].Destination) + assert.Equal(t, u[1].Address, tx0.Operations[0].Source) + + // Check balance on m[1] + balance, err := model.LoadCanonicalBalanceByAssetHolder(m[0].Ctx, + a[0].Name, u[1].Address) + assert.Nil(t, err) + // The balance should be 10 (unchanged) because you've settled 5 to + // yourself (no change in balance) to issue some of your own asset to + // yourself (no change of balance again). So, really it dtrt. + assert.Equal(t, big.NewInt(10), (*big.Int)(&balance.Value)) +}