diff --git a/protocols/horizon/effects/main.go b/protocols/horizon/effects/main.go index 7b7a4ed91d..3da8bb6239 100644 --- a/protocols/horizon/effects/main.go +++ b/protocols/horizon/effects/main.go @@ -78,6 +78,10 @@ const ( // it issues. EffectTrustlineDeauthorized EffectType = 24 // from allow_trust + // EffectTrustlineAuthorizedToMaintainLiabilities occurs when an anchor has AUTH_REQUIRED flag set + // to true and it authorizes another account's trustline to maintain liabilities + EffectTrustlineAuthorizedToMaintainLiabilities EffectType = 25 // from allow_trust + // trading effects // EffectOfferCreated occurs when an account offers to trade an asset @@ -114,30 +118,31 @@ const ( // EffectTypeNames stores a map of effect type ID and names var EffectTypeNames = map[EffectType]string{ - EffectAccountCreated: "account_created", - EffectAccountRemoved: "account_removed", - EffectAccountCredited: "account_credited", - EffectAccountDebited: "account_debited", - EffectAccountThresholdsUpdated: "account_thresholds_updated", - EffectAccountHomeDomainUpdated: "account_home_domain_updated", - EffectAccountFlagsUpdated: "account_flags_updated", - EffectAccountInflationDestinationUpdated: "account_inflation_destination_updated", - EffectSignerCreated: "signer_created", - EffectSignerRemoved: "signer_removed", - EffectSignerUpdated: "signer_updated", - EffectTrustlineCreated: "trustline_created", - EffectTrustlineRemoved: "trustline_removed", - EffectTrustlineUpdated: "trustline_updated", - EffectTrustlineAuthorized: "trustline_authorized", - EffectTrustlineDeauthorized: "trustline_deauthorized", - EffectOfferCreated: "offer_created", - EffectOfferRemoved: "offer_removed", - EffectOfferUpdated: "offer_updated", - EffectTrade: "trade", - EffectDataCreated: "data_created", - EffectDataRemoved: "data_removed", - EffectDataUpdated: "data_updated", - EffectSequenceBumped: "sequence_bumped", + EffectAccountCreated: "account_created", + EffectAccountRemoved: "account_removed", + EffectAccountCredited: "account_credited", + EffectAccountDebited: "account_debited", + EffectAccountThresholdsUpdated: "account_thresholds_updated", + EffectAccountHomeDomainUpdated: "account_home_domain_updated", + EffectAccountFlagsUpdated: "account_flags_updated", + EffectAccountInflationDestinationUpdated: "account_inflation_destination_updated", + EffectSignerCreated: "signer_created", + EffectSignerRemoved: "signer_removed", + EffectSignerUpdated: "signer_updated", + EffectTrustlineCreated: "trustline_created", + EffectTrustlineRemoved: "trustline_removed", + EffectTrustlineUpdated: "trustline_updated", + EffectTrustlineAuthorized: "trustline_authorized", + EffectTrustlineAuthorizedToMaintainLiabilities: "trustline_authorized_to_maintain_liabilities", + EffectTrustlineDeauthorized: "trustline_deauthorized", + EffectOfferCreated: "offer_created", + EffectOfferRemoved: "offer_removed", + EffectOfferUpdated: "offer_updated", + EffectTrade: "trade", + EffectDataCreated: "data_created", + EffectDataRemoved: "data_removed", + EffectDataUpdated: "data_updated", + EffectSequenceBumped: "sequence_bumped", } // Base provides the common structure for any effect resource effect. @@ -247,6 +252,13 @@ type TrustlineAuthorized struct { AssetCode string `json:"asset_code,omitempty"` } +type TrustlineAuthorizedToMaintainLiabilities struct { + Base + Trustor string `json:"trustor"` + AssetType string `json:"asset_type"` + AssetCode string `json:"asset_code,omitempty"` +} + type TrustlineDeauthorized struct { Base Trustor string `json:"trustor"` @@ -397,7 +409,7 @@ func UnmarshalEffect(effectType string, dataString []byte) (effects Effect, err return } effects = effect - case EffectTypeNames[EffectTrustlineAuthorized]: + case EffectTypeNames[EffectTrustlineAuthorized], EffectTypeNames[EffectTrustlineAuthorizedToMaintainLiabilities]: var effect TrustlineAuthorized if err = json.Unmarshal(dataString, &effect); err != nil { return diff --git a/protocols/horizon/main.go b/protocols/horizon/main.go index e8e0ccf1f4..b7a2ef7868 100644 --- a/protocols/horizon/main.go +++ b/protocols/horizon/main.go @@ -192,12 +192,13 @@ func (res AssetStat) PagingToken() string { // Balance represents an account's holdings for a single currency type type Balance struct { - Balance string `json:"balance"` - Limit string `json:"limit,omitempty"` - BuyingLiabilities string `json:"buying_liabilities"` - SellingLiabilities string `json:"selling_liabilities"` - LastModifiedLedger uint32 `json:"last_modified_ledger,omitempty"` - IsAuthorized *bool `json:"is_authorized,omitempty"` + Balance string `json:"balance"` + Limit string `json:"limit,omitempty"` + BuyingLiabilities string `json:"buying_liabilities"` + SellingLiabilities string `json:"selling_liabilities"` + LastModifiedLedger uint32 `json:"last_modified_ledger,omitempty"` + IsAuthorized *bool `json:"is_authorized,omitempty"` + IsAuthorizedToMaintainLiabilities *bool `json:"is_authorized_to_maintain_liabilities,omitempty"` base.Asset } diff --git a/protocols/horizon/operations/main.go b/protocols/horizon/operations/main.go index 479f0c3161..edb2d065bf 100644 --- a/protocols/horizon/operations/main.go +++ b/protocols/horizon/operations/main.go @@ -191,9 +191,10 @@ type ChangeTrust struct { type AllowTrust struct { Base base.Asset - Trustee string `json:"trustee"` - Trustor string `json:"trustor"` - Authorize bool `json:"authorize"` + Trustee string `json:"trustee"` + Trustor string `json:"trustor"` + Authorize bool `json:"authorize"` + AuthorizeToMaintainLiabilities bool `json:"authorize_to_maintain_liabilities"` } // AccountMerge is the json resource representing a single operation whose type diff --git a/services/horizon/CHANGELOG.md b/services/horizon/CHANGELOG.md index 4ba29de7e1..c939a256d2 100644 --- a/services/horizon/CHANGELOG.md +++ b/services/horizon/CHANGELOG.md @@ -9,7 +9,64 @@ file. This project adheres to [Semantic Versioning](http://semver.org/). * Fix ask and bid price levels of GET /order_book when encountering non-canonical price values. The `limit` parameter is now respected and levels are coallesced properly. Also, `price_r` is now in canonical form. * Remove duplicate indexes: index_history_transactions_on_id, index_history_ledgers_on_id, exp_asset_stats_by_code, and asset_by_code * Remove asset_stats table which is no longer necessary - +* Add support for CAP0018: Fine-Grained Control of Authorization ([#2423](https://github.com/stellar/go/pull/2423)). + - Add `is_authorized_to_maintain_liabilities` to `Balance`. +
+ "balances": [ + { + "is_authorized": true, + "is_authorized_to_maintain_liabilities": true, + "balance": "27.1374422", + "limit": "922337203685.4775807", + "buying_liabilities": "0.0000000", + "selling_liabilities": "0.0000000", + "last_modified_ledger": 28893780, + "asset_type": "credit_alphanum4", + "asset_code": "USD", + "asset_issuer": "GBSTRUSD7IRX73RQZBL3RQUH6KS3O4NYFY3QCALDLZD77XMZOPWAVTUK" + }, + { + "balance": "1.5000000", + "buying_liabilities": "0.0000000", + "selling_liabilities": "0.0000000", + "asset_type": "native" + } + ] ++ - Add `authorize_to_maintain_liabilities` to `AllowTrust` operation. +
+ { + "id": "124042211741474817", + "paging_token": "124042211741474817", + "transaction_successful": true, + "source_account": "GBSTRUSD7IRX73RQZBL3RQUH6KS3O4NYFY3QCALDLZD77XMZOPWAVTUK", + "type": "allow_trust", + "type_i": 7, + "created_at": "2020-03-27T03:40:10Z", + "transaction_hash": "a77d4ee5346d55fb8026cdcdad6e4b5e0c440c96b4627e3727f4ccfa6d199e94", + "asset_type": "credit_alphanum4", + "asset_code": "USD", + "asset_issuer": "GBSTRUSD7IRX73RQZBL3RQUH6KS3O4NYFY3QCALDLZD77XMZOPWAVTUK", + "trustee": "GBSTRUSD7IRX73RQZBL3RQUH6KS3O4NYFY3QCALDLZD77XMZOPWAVTUK", + "trustor": "GA332TXN6BX2DYKGYB7FW5BWV2JLQKERNX4T7EUJT4MHWOW2TSGC2SPM", + "authorize": true, + "authorize_to_maintain_liabilities": true, + } ++ - Add effect `trustline_authorized_to_maintain_liabilities`. +
+ { + "id": "0124042211741474817-0000000001", + "paging_token": "124042211741474817-1", + "account": "GBSTRUSD7IRX73RQZBL3RQUH6KS3O4NYFY3QCALDLZD77XMZOPWAVTUK", + "type": "trustline_authorized_to_maintain_liabilities", + "type_i": 25, + "created_at": "2020-03-27T03:40:10Z", + "trustor": "GA332TXN6BX2DYKGYB7FW5BWV2JLQKERNX4T7EUJT4MHWOW2TSGC2SPM", + "asset_type": "credit_alphanum4", + "asset_code": "USD" + } +## v1.0.1 ### Fixed diff --git a/services/horizon/internal/db2/core/trustline.go b/services/horizon/internal/db2/core/trustline.go index 122aa5c059..d0cac3012e 100644 --- a/services/horizon/internal/db2/core/trustline.go +++ b/services/horizon/internal/db2/core/trustline.go @@ -11,6 +11,10 @@ func (tl Trustline) IsAuthorized() bool { return (tl.Flags & int32(xdr.TrustLineFlagsAuthorizedFlag)) != 0 } +func (tl Trustline) IsAuthorizedToMaintainLiabilities() bool { + return (tl.Flags & int32(xdr.TrustLineFlagsAuthorizedToMaintainLiabilitiesFlag)) != 0 +} + // AssetsForAddress returns a list of assets and balances for those assets held by // a given address. func (q *Q) AssetsForAddress(addy string) ([]xdr.Asset, []xdr.Int64, error) { diff --git a/services/horizon/internal/db2/history/main.go b/services/horizon/internal/db2/history/main.go index e9452cde53..10032e0e26 100644 --- a/services/horizon/internal/db2/history/main.go +++ b/services/horizon/internal/db2/history/main.go @@ -81,6 +81,10 @@ const ( // it issues. EffectTrustlineDeauthorized EffectType = 24 // from allow_trust + // EffectTrustlineAuthorizedToMaintainLiabilities occurs when an anchor has AUTH_REQUIRED flag set + // to true and it authorizes another account's trustline to maintain liabilities + EffectTrustlineAuthorizedToMaintainLiabilities EffectType = 25 // from allow_trust + // trading effects // EffectOfferCreated occurs when an account offers to trade an asset diff --git a/services/horizon/internal/db2/history/trust_lines.go b/services/horizon/internal/db2/history/trust_lines.go index d5dff67f91..6512676d5b 100644 --- a/services/horizon/internal/db2/history/trust_lines.go +++ b/services/horizon/internal/db2/history/trust_lines.go @@ -15,6 +15,12 @@ func (trustLine TrustLine) IsAuthorized() bool { return xdr.TrustLineFlags(trustLine.Flags).IsAuthorized() } +// IsAuthorizedToMaintainLiabilities returns true if issuer has authorized the account to maintain +// liabilities with its credit +func (trustLine TrustLine) IsAuthorizedToMaintainLiabilities() bool { + return xdr.TrustLineFlags(trustLine.Flags).IsAuthorizedToMaintainLiabilitiesFlag() +} + func (q *Q) CountTrustLines() (int, error) { sql := sq.Select("count(*)").From("trust_lines") diff --git a/services/horizon/internal/expingest/processors/effects_processor.go b/services/horizon/internal/expingest/processors/effects_processor.go index 56d3cbc87a..70acaa58b5 100644 --- a/services/horizon/internal/expingest/processors/effects_processor.go +++ b/services/horizon/internal/expingest/processors/effects_processor.go @@ -549,9 +549,16 @@ func (operation *transactionOperationWrapper) allowTrustEffects() []effect { } assetDetails(details, asset, "") - if xdr.TrustLineFlags(op.Authorize).IsAuthorized() { + switch { + case xdr.TrustLineFlags(op.Authorize).IsAuthorized(): effects.add(source.Address(), history.EffectTrustlineAuthorized, details) - } else { + case xdr.TrustLineFlags(op.Authorize).IsAuthorizedToMaintainLiabilitiesFlag(): + effects.add( + source.Address(), + history.EffectTrustlineAuthorizedToMaintainLiabilities, + details, + ) + default: effects.add(source.Address(), history.EffectTrustlineDeauthorized, details) } diff --git a/services/horizon/internal/expingest/processors/effects_processor_test.go b/services/horizon/internal/expingest/processors/effects_processor_test.go index 375356a83f..5f11414b62 100644 --- a/services/horizon/internal/expingest/processors/effects_processor_test.go +++ b/services/horizon/internal/expingest/processors/effects_processor_test.go @@ -1213,3 +1213,48 @@ func TestOperationRegressionAccountTrustItself(t *testing.T) { tt.NoError(err) tt.Equal([]effect{}, effects) } + +func TestOperationEffectsAllowTrustAuthorizedToMaintainLiabilities(t *testing.T) { + tt := assert.New(t) + asset := xdr.Asset{} + allowTrustAsset, err := asset.ToAllowTrustOpAsset("COP") + tt.NoError(err) + source := xdr.MustAddress("GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD") + op := xdr.Operation{ + SourceAccount: &source, + Body: xdr.OperationBody{ + Type: xdr.OperationTypeAllowTrust, + AllowTrustOp: &xdr.AllowTrustOp{ + Trustor: xdr.MustAddress("GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3"), + Asset: allowTrustAsset, + Authorize: xdr.Uint32(xdr.TrustLineFlagsAuthorizedToMaintainLiabilitiesFlag), + }, + }, + } + + operation := transactionOperationWrapper{ + index: 0, + transaction: io.LedgerTransaction{}, + operation: op, + ledgerSequence: 1, + } + + effects, err := operation.effects() + tt.NoError(err) + + expected := []effect{ + effect{ + address: "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + operationID: 4294967297, + details: map[string]interface{}{ + "asset_code": "COP", + "asset_issuer": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "asset_type": "credit_alphanum4", + "trustor": "GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3", + }, + effectType: history.EffectTrustlineAuthorizedToMaintainLiabilities, + order: uint32(1), + }, + } + tt.Equal(expected, effects) +} diff --git a/services/horizon/internal/expingest/processors/operations_processor.go b/services/horizon/internal/expingest/processors/operations_processor.go index adac726d25..88255b48fe 100644 --- a/services/horizon/internal/expingest/processors/operations_processor.go +++ b/services/horizon/internal/expingest/processors/operations_processor.go @@ -258,6 +258,7 @@ func (operation *transactionOperationWrapper) Details() map[string]interface{} { details["trustee"] = source.Address() details["trustor"] = op.Trustor.Address() details["authorize"] = xdr.TrustLineFlags(op.Authorize).IsAuthorized() + details["authorize_to_maintain_liabilities"] = xdr.TrustLineFlags(op.Authorize).IsAuthorized() || xdr.TrustLineFlags(op.Authorize).IsAuthorizedToMaintainLiabilitiesFlag() case xdr.OperationTypeAccountMerge: aid := operation.operation.Body.MustDestination() details["account"] = source.Address() diff --git a/services/horizon/internal/expingest/processors/transaction_operation_wrapper_test.go b/services/horizon/internal/expingest/processors/transaction_operation_wrapper_test.go index d5b8b2a8e6..4f6cd06f31 100644 --- a/services/horizon/internal/expingest/processors/transaction_operation_wrapper_test.go +++ b/services/horizon/internal/expingest/processors/transaction_operation_wrapper_test.go @@ -3,6 +3,7 @@ package processors import ( "testing" + "github.com/stellar/go/exp/ingest/io" . "github.com/stellar/go/services/horizon/internal/test/transactions" "github.com/stellar/go/xdr" "github.com/stretchr/testify/assert" @@ -442,12 +443,13 @@ func TestTransactionOperationDetails(t *testing.T) { hash: "6d2e30fd57492bf2e2b132e1bc91a548a369189bebf77eb2b3d829121a9d2c50", index: 0, expected: map[string]interface{}{ - "asset_code": "USD", - "asset_issuer": "GD4SMOE3VPSF7ZR3CTEQ3P5UNTBMEJDA2GLXTHR7MMARANKKJDZ7RPGF", - "asset_type": "credit_alphanum4", - "authorize": true, - "trustee": "GD4SMOE3VPSF7ZR3CTEQ3P5UNTBMEJDA2GLXTHR7MMARANKKJDZ7RPGF", - "trustor": "GCVW5LCRZFP7PENXTAGOVIQXADDNUXXZJCNKF4VQB2IK7W2LPJWF73UG", + "asset_code": "USD", + "asset_issuer": "GD4SMOE3VPSF7ZR3CTEQ3P5UNTBMEJDA2GLXTHR7MMARANKKJDZ7RPGF", + "asset_type": "credit_alphanum4", + "authorize": true, + "authorize_to_maintain_liabilities": true, + "trustee": "GD4SMOE3VPSF7ZR3CTEQ3P5UNTBMEJDA2GLXTHR7MMARANKKJDZ7RPGF", + "trustor": "GCVW5LCRZFP7PENXTAGOVIQXADDNUXXZJCNKF4VQB2IK7W2LPJWF73UG", }, }, { @@ -857,3 +859,99 @@ func TestOperationParticipants(t *testing.T) { tt.Empty(result) } +func TestTransactionOperationAllowTrustDetails(t *testing.T) { + tt := assert.New(t) + asset := xdr.Asset{} + allowTrustAsset, err := asset.ToAllowTrustOpAsset("COP") + tt.NoError(err) + + source := xdr.MustAddress("GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD") + + testCases := []struct { + desc string + op xdr.Operation + expected map[string]interface{} + }{ + { + desc: "authorize", + op: xdr.Operation{ + SourceAccount: &source, + Body: xdr.OperationBody{ + Type: xdr.OperationTypeAllowTrust, + AllowTrustOp: &xdr.AllowTrustOp{ + Trustor: xdr.MustAddress("GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3"), + Asset: allowTrustAsset, + Authorize: xdr.Uint32(xdr.TrustLineFlagsAuthorizedFlag), + }, + }, + }, + expected: map[string]interface{}{ + "asset_code": "COP", + "asset_issuer": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "asset_type": "credit_alphanum4", + "authorize": true, + "authorize_to_maintain_liabilities": true, + "trustee": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "trustor": "GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3", + }, + }, + { + desc: "authorize maintain liabilities", + op: xdr.Operation{ + SourceAccount: &source, + Body: xdr.OperationBody{ + Type: xdr.OperationTypeAllowTrust, + AllowTrustOp: &xdr.AllowTrustOp{ + Trustor: xdr.MustAddress("GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3"), + Asset: allowTrustAsset, + Authorize: xdr.Uint32(xdr.TrustLineFlagsAuthorizedToMaintainLiabilitiesFlag), + }, + }, + }, + expected: map[string]interface{}{ + "asset_code": "COP", + "asset_issuer": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "asset_type": "credit_alphanum4", + "authorize": false, + "authorize_to_maintain_liabilities": true, + "trustee": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "trustor": "GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3", + }, + }, + { + desc: "deauthorize", + op: xdr.Operation{ + SourceAccount: &source, + Body: xdr.OperationBody{ + Type: xdr.OperationTypeAllowTrust, + AllowTrustOp: &xdr.AllowTrustOp{ + Trustor: xdr.MustAddress("GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3"), + Asset: allowTrustAsset, + Authorize: xdr.Uint32(0), + }, + }, + }, + expected: map[string]interface{}{ + "asset_code": "COP", + "asset_issuer": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "asset_type": "credit_alphanum4", + "authorize": false, + "authorize_to_maintain_liabilities": false, + "trustee": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "trustor": "GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3", + }, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + operation := transactionOperationWrapper{ + index: 0, + transaction: io.LedgerTransaction{}, + operation: tc.op, + ledgerSequence: 1, + } + + tt.Equal(tc.expected, operation.Details()) + }) + } +} diff --git a/services/horizon/internal/resourceadapter/balance.go b/services/horizon/internal/resourceadapter/balance.go index b0fd3f2445..834804240a 100644 --- a/services/horizon/internal/resourceadapter/balance.go +++ b/services/horizon/internal/resourceadapter/balance.go @@ -25,6 +25,11 @@ func PopulateBalance(dest *protocol.Balance, row core.Trustline) (err error) { dest.LastModifiedLedger = row.LastModified isAuthorized := row.IsAuthorized() dest.IsAuthorized = &isAuthorized + dest.IsAuthorizedToMaintainLiabilities = &isAuthorized + isAuthorizedToMaintainLiabilities := row.IsAuthorizedToMaintainLiabilities() + if isAuthorizedToMaintainLiabilities { + dest.IsAuthorizedToMaintainLiabilities = &isAuthorizedToMaintainLiabilities + } return } @@ -43,6 +48,11 @@ func PopulateHistoryBalance(dest *protocol.Balance, row history.TrustLine) (err dest.LastModifiedLedger = row.LastModifiedLedger isAuthorized := row.IsAuthorized() dest.IsAuthorized = &isAuthorized + dest.IsAuthorizedToMaintainLiabilities = &isAuthorized + isAuthorizedToMaintainLiabilities := row.IsAuthorizedToMaintainLiabilities() + if isAuthorizedToMaintainLiabilities { + dest.IsAuthorizedToMaintainLiabilities = &isAuthorizedToMaintainLiabilities + } return } diff --git a/services/horizon/internal/resourceadapter/balance_test.go b/services/horizon/internal/resourceadapter/balance_test.go index fbf013e99b..625bc559bf 100644 --- a/services/horizon/internal/resourceadapter/balance_test.go +++ b/services/horizon/internal/resourceadapter/balance_test.go @@ -22,6 +22,15 @@ func TestPopulateBalance(t *testing.T) { Balance: 10, Flags: 1, } + authorizedToMaintainLiabilitiesTrustline := core.Trustline{ + Accountid: "testID", + Assettype: xdr.AssetTypeAssetTypeCreditAlphanum12, + Issuer: "", + Assetcode: testAssetCode1, + Tlimit: 100, + Balance: 10, + Flags: 2, + } unauthorizedTrustline := core.Trustline{ Accountid: "testID", Assettype: xdr.AssetTypeAssetTypeCreditAlphanum12, @@ -29,7 +38,7 @@ func TestPopulateBalance(t *testing.T) { Assetcode: testAssetCode2, Tlimit: 100, Balance: 10, - Flags: 2, + Flags: 0, } want := Balance{} @@ -41,6 +50,18 @@ func TestPopulateBalance(t *testing.T) { assert.Equal(t, "", want.Issuer) assert.Equal(t, testAssetCode1, want.Code) assert.Equal(t, true, *want.IsAuthorized) + assert.Equal(t, true, *want.IsAuthorizedToMaintainLiabilities) + + want = Balance{} + err = PopulateBalance(&want, authorizedToMaintainLiabilitiesTrustline) + assert.NoError(t, err) + assert.Equal(t, "credit_alphanum12", want.Type) + assert.Equal(t, "0.0000010", want.Balance) + assert.Equal(t, "0.0000100", want.Limit) + assert.Equal(t, "", want.Issuer) + assert.Equal(t, testAssetCode1, want.Code) + assert.Equal(t, false, *want.IsAuthorized) + assert.Equal(t, true, *want.IsAuthorizedToMaintainLiabilities) want = Balance{} err = PopulateBalance(&want, unauthorizedTrustline) @@ -51,6 +72,7 @@ func TestPopulateBalance(t *testing.T) { assert.Equal(t, "", want.Issuer) assert.Equal(t, testAssetCode2, want.Code) assert.Equal(t, false, *want.IsAuthorized) + assert.Equal(t, false, *want.IsAuthorizedToMaintainLiabilities) } func TestPopulateHistoryBalance(t *testing.T) { @@ -65,6 +87,15 @@ func TestPopulateHistoryBalance(t *testing.T) { Balance: 10, Flags: 1, } + authorizedToMaintainLiabilitiesTrustline := history.TrustLine{ + AccountID: "testID", + AssetType: xdr.AssetTypeAssetTypeCreditAlphanum12, + AssetIssuer: "", + AssetCode: testAssetCode1, + Limit: 100, + Balance: 10, + Flags: 2, + } unauthorizedTrustline := history.TrustLine{ AccountID: "testID", AssetType: xdr.AssetTypeAssetTypeCreditAlphanum12, @@ -72,7 +103,7 @@ func TestPopulateHistoryBalance(t *testing.T) { AssetCode: testAssetCode2, Limit: 100, Balance: 10, - Flags: 2, + Flags: 0, } want := Balance{} @@ -84,6 +115,18 @@ func TestPopulateHistoryBalance(t *testing.T) { assert.Equal(t, "", want.Issuer) assert.Equal(t, testAssetCode1, want.Code) assert.Equal(t, true, *want.IsAuthorized) + assert.Equal(t, true, *want.IsAuthorizedToMaintainLiabilities) + + want = Balance{} + err = PopulateHistoryBalance(&want, authorizedToMaintainLiabilitiesTrustline) + assert.NoError(t, err) + assert.Equal(t, "credit_alphanum12", want.Type) + assert.Equal(t, "0.0000010", want.Balance) + assert.Equal(t, "0.0000100", want.Limit) + assert.Equal(t, "", want.Issuer) + assert.Equal(t, testAssetCode1, want.Code) + assert.Equal(t, false, *want.IsAuthorized) + assert.Equal(t, true, *want.IsAuthorizedToMaintainLiabilities) want = Balance{} err = PopulateHistoryBalance(&want, unauthorizedTrustline) @@ -94,6 +137,7 @@ func TestPopulateHistoryBalance(t *testing.T) { assert.Equal(t, "", want.Issuer) assert.Equal(t, testAssetCode2, want.Code) assert.Equal(t, false, *want.IsAuthorized) + assert.Equal(t, false, *want.IsAuthorizedToMaintainLiabilities) } func TestPopulateNativeBalance(t *testing.T) { diff --git a/services/horizon/internal/resourceadapter/effects.go b/services/horizon/internal/resourceadapter/effects.go index fa792e0326..8a75e5bcd8 100644 --- a/services/horizon/internal/resourceadapter/effects.go +++ b/services/horizon/internal/resourceadapter/effects.go @@ -11,30 +11,31 @@ import ( ) var EffectTypeNames = map[history.EffectType]string{ - history.EffectAccountCreated: "account_created", - history.EffectAccountRemoved: "account_removed", - history.EffectAccountCredited: "account_credited", - history.EffectAccountDebited: "account_debited", - history.EffectAccountThresholdsUpdated: "account_thresholds_updated", - history.EffectAccountHomeDomainUpdated: "account_home_domain_updated", - history.EffectAccountFlagsUpdated: "account_flags_updated", - history.EffectAccountInflationDestinationUpdated: "account_inflation_destination_updated", - history.EffectSignerCreated: "signer_created", - history.EffectSignerRemoved: "signer_removed", - history.EffectSignerUpdated: "signer_updated", - history.EffectTrustlineCreated: "trustline_created", - history.EffectTrustlineRemoved: "trustline_removed", - history.EffectTrustlineUpdated: "trustline_updated", - history.EffectTrustlineAuthorized: "trustline_authorized", - history.EffectTrustlineDeauthorized: "trustline_deauthorized", - history.EffectOfferCreated: "offer_created", - history.EffectOfferRemoved: "offer_removed", - history.EffectOfferUpdated: "offer_updated", - history.EffectTrade: "trade", - history.EffectDataCreated: "data_created", - history.EffectDataRemoved: "data_removed", - history.EffectDataUpdated: "data_updated", - history.EffectSequenceBumped: "sequence_bumped", + history.EffectAccountCreated: "account_created", + history.EffectAccountRemoved: "account_removed", + history.EffectAccountCredited: "account_credited", + history.EffectAccountDebited: "account_debited", + history.EffectAccountThresholdsUpdated: "account_thresholds_updated", + history.EffectAccountHomeDomainUpdated: "account_home_domain_updated", + history.EffectAccountFlagsUpdated: "account_flags_updated", + history.EffectAccountInflationDestinationUpdated: "account_inflation_destination_updated", + history.EffectSignerCreated: "signer_created", + history.EffectSignerRemoved: "signer_removed", + history.EffectSignerUpdated: "signer_updated", + history.EffectTrustlineCreated: "trustline_created", + history.EffectTrustlineRemoved: "trustline_removed", + history.EffectTrustlineUpdated: "trustline_updated", + history.EffectTrustlineAuthorized: "trustline_authorized", + history.EffectTrustlineAuthorizedToMaintainLiabilities: "trustline_authorized_to_maintain_liabilities", + history.EffectTrustlineDeauthorized: "trustline_deauthorized", + history.EffectOfferCreated: "offer_created", + history.EffectOfferRemoved: "offer_removed", + history.EffectOfferUpdated: "offer_updated", + history.EffectTrade: "trade", + history.EffectDataCreated: "data_created", + history.EffectDataRemoved: "data_removed", + history.EffectDataUpdated: "data_updated", + history.EffectSequenceBumped: "sequence_bumped", } // NewEffect creates a new effect resource from the provided database representation @@ -101,6 +102,10 @@ func NewEffect( e := effects.TrustlineAuthorized{Base: basev} err = row.UnmarshalDetails(&e) result = e + case history.EffectTrustlineAuthorizedToMaintainLiabilities: + e := effects.TrustlineAuthorizedToMaintainLiabilities{Base: basev} + err = row.UnmarshalDetails(&e) + result = e case history.EffectTrustlineDeauthorized: e := effects.TrustlineDeauthorized{Base: basev} err = row.UnmarshalDetails(&e) diff --git a/services/horizon/internal/resourceadapter/effects_test.go b/services/horizon/internal/resourceadapter/effects_test.go new file mode 100644 index 0000000000..363afd3952 --- /dev/null +++ b/services/horizon/internal/resourceadapter/effects_test.go @@ -0,0 +1,37 @@ +package resourceadapter + +import ( + "testing" + + "github.com/guregu/null" + "github.com/stellar/go/protocols/horizon/effects" + "github.com/stellar/go/services/horizon/internal/db2/history" + "github.com/stellar/go/support/test" + "github.com/stretchr/testify/assert" +) + +func TestNewEffect_EffectTrustlineAuthorizedToMaintainLiabilities(t *testing.T) { + tt := assert.New(t) + ctx, _ := test.ContextWithLogBuffer() + + details := `{ + "asset_code": "COP", + "asset_issuer": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "asset_type": "credit_alphanum4", + "trustor": "GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3" + }` + + hEffect := history.Effect{ + Account: "GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3", + HistoryOperationID: 1, + Order: 1, + Type: history.EffectTrustlineAuthorizedToMaintainLiabilities, + DetailsString: null.StringFrom(details), + } + resource, err := NewEffect(ctx, hEffect, history.Ledger{}) + tt.NoError(err) + + effect, ok := resource.(effects.TrustlineAuthorizedToMaintainLiabilities) + tt.True(ok) + tt.Equal("trustline_authorized_to_maintain_liabilities", effect.Type) +} diff --git a/services/horizon/internal/resourceadapter/operations_test.go b/services/horizon/internal/resourceadapter/operations_test.go index 0bef78daba..026878506d 100644 --- a/services/horizon/internal/resourceadapter/operations_test.go +++ b/services/horizon/internal/resourceadapter/operations_test.go @@ -1,11 +1,14 @@ package resourceadapter import ( + "encoding/json" "testing" + "github.com/guregu/null" "github.com/stellar/go/protocols/horizon/operations" "github.com/stellar/go/services/horizon/internal/db2/history" "github.com/stellar/go/support/test" + "github.com/stellar/go/xdr" "github.com/stretchr/testify/assert" ) @@ -67,3 +70,74 @@ func TestPopulateOperation_WithTransaction(t *testing.T) { assert.Equal(t, int32(100), dest.Transaction.FeeCharged) assert.Equal(t, int32(10000), dest.Transaction.MaxFee) } + +func TestPopulateOperation_AllowTrust(t *testing.T) { + tt := assert.New(t) + + details := `{ + "asset_code": "COP", + "asset_issuer": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "asset_type": "credit_alphanum4", + "authorize": false, + "authorize_to_maintain_liabilities": true, + "trustee": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "trustor": "GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3" + }` + + rsp, err := getJSONResponse(details) + tt.NoError(err) + tt.Equal(false, rsp["authorize"]) + tt.Equal(true, rsp["authorize_to_maintain_liabilities"]) + + details = `{ + "asset_code": "COP", + "asset_issuer": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "asset_type": "credit_alphanum4", + "authorize": true, + "authorize_to_maintain_liabilities": true, + "trustee": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "trustor": "GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3" + }` + + rsp, err = getJSONResponse(details) + tt.NoError(err) + tt.Equal(true, rsp["authorize"]) + tt.Equal(true, rsp["authorize_to_maintain_liabilities"]) + + details = `{ + "asset_code": "COP", + "asset_issuer": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "asset_type": "credit_alphanum4", + "authorize": false, + "authorize_to_maintain_liabilities": false, + "trustee": "GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD", + "trustor": "GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3" + }` + + rsp, err = getJSONResponse(details) + tt.NoError(err) + tt.Equal(false, rsp["authorize"]) + tt.Equal(false, rsp["authorize_to_maintain_liabilities"]) +} + +func getJSONResponse(details string) (rsp map[string]interface{}, err error) { + ctx, _ := test.ContextWithLogBuffer() + txSuccessful := true + transactionRow := history.Transaction{Successful: &txSuccessful, MaxFee: 10000, FeeCharged: 100} + operationsRow := history.Operation{ + TransactionSuccessful: &txSuccessful, + Type: xdr.OperationTypeAllowTrust, + DetailsString: null.StringFrom(details), + } + resource, err := NewOperation(ctx, operationsRow, &transactionRow, history.Ledger{}) + if err != nil { + return + } + + data, err := json.Marshal(resource) + if err != nil { + return + } + err = json.Unmarshal(data, &rsp) + return +}