diff --git a/db/migrations/postgres/000042_rename_tokenaccount_to_tokenbalance.down.sql b/db/migrations/postgres/000042_rename_tokenaccount_to_tokenbalance.down.sql new file mode 100644 index 0000000000..747833dcef --- /dev/null +++ b/db/migrations/postgres/000042_rename_tokenaccount_to_tokenbalance.down.sql @@ -0,0 +1,3 @@ +BEGIN; +ALTER TABLE tokenbalance RENAME TO tokenaccount; +COMMIT: diff --git a/db/migrations/postgres/000042_rename_tokenaccount_to_tokenbalance.up.sql b/db/migrations/postgres/000042_rename_tokenaccount_to_tokenbalance.up.sql new file mode 100644 index 0000000000..a99c49f24e --- /dev/null +++ b/db/migrations/postgres/000042_rename_tokenaccount_to_tokenbalance.up.sql @@ -0,0 +1,3 @@ +BEGIN; +ALTER TABLE tokenaccount RENAME TO tokenbalance; +COMMIT; diff --git a/db/migrations/sqlite/000042_rename_tokenaccount_to_tokenbalance.down.sql b/db/migrations/sqlite/000042_rename_tokenaccount_to_tokenbalance.down.sql new file mode 100644 index 0000000000..35d03d9a95 --- /dev/null +++ b/db/migrations/sqlite/000042_rename_tokenaccount_to_tokenbalance.down.sql @@ -0,0 +1 @@ +ALTER TABLE tokenbalance RENAME TO tokenaccount; diff --git a/db/migrations/sqlite/000042_rename_tokenaccount_to_tokenbalance.up.sql b/db/migrations/sqlite/000042_rename_tokenaccount_to_tokenbalance.up.sql new file mode 100644 index 0000000000..c98f91c81b --- /dev/null +++ b/db/migrations/sqlite/000042_rename_tokenaccount_to_tokenbalance.up.sql @@ -0,0 +1 @@ +ALTER TABLE tokenaccount RENAME TO tokenbalance; diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index b0a323d243..f9a28cb824 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -4128,6 +4128,7 @@ paths: description: "" /namespaces/{ns}/tokens/{type}/pools: get: + deprecated: true description: 'TODO: Description' operationId: getTokenPoolsByType parameters: @@ -4281,6 +4282,7 @@ paths: default: description: "" post: + deprecated: true description: 'TODO: Description' operationId: postTokenPoolByType parameters: @@ -4404,6 +4406,7 @@ paths: description: "" /namespaces/{ns}/tokens/{type}/pools/{name}: get: + deprecated: true description: 'TODO: Description' operationId: getTokenPoolByName parameters: @@ -4473,6 +4476,7 @@ paths: description: "" /namespaces/{ns}/tokens/{type}/pools/{name}/accounts: get: + deprecated: true description: 'TODO: Description' operationId: getTokenAccountsByPool parameters: @@ -4597,6 +4601,7 @@ paths: description: "" /namespaces/{ns}/tokens/{type}/pools/{name}/burn: post: + deprecated: true description: 'TODO: Description' operationId: postTokenBurnByType parameters: @@ -4817,6 +4822,7 @@ paths: description: "" /namespaces/{ns}/tokens/{type}/pools/{name}/mint: post: + deprecated: true description: 'TODO: Description' operationId: postTokenMintByType parameters: @@ -5037,6 +5043,7 @@ paths: description: "" /namespaces/{ns}/tokens/{type}/pools/{name}/transfers: get: + deprecated: true description: 'TODO: Description' operationId: getTokenTransfersByPool parameters: @@ -5201,6 +5208,7 @@ paths: default: description: "" post: + deprecated: true description: 'TODO: Description' operationId: postTokenTransferByType parameters: @@ -5424,6 +5432,108 @@ paths: description: 'TODO: Description' operationId: getTokenAccounts parameters: + - description: 'TODO: Description' + in: path + name: ns + required: true + schema: + example: default + type: string + - description: Server-side request timeout (millseconds, or set a custom suffix + like 10s) + in: header + name: Request-Timeout + schema: + default: 120s + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: balance + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: connector + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: key + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: namespace + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: poolprotocolid + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: tokenindex + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: updated + schema: + type: string + - description: Sort field. For multi-field sort use comma separated values (or + multiple query values) with '-' prefix for descending + in: query + name: sort + schema: + type: string + - description: Ascending sort order (overrides all fields in a multi-field sort) + in: query + name: ascending + schema: + type: string + - description: Descending sort order (overrides all fields in a multi-field + sort) + in: query + name: descending + schema: + type: string + - description: 'The number of records to skip (max: 1,000). Unsuitable for bulk + operations' + in: query + name: skip + schema: + type: string + - description: 'The maximum number of records to return (max: 1,000)' + in: query + name: limit + schema: + example: "25" + type: string + - description: Return a total count as well as items (adds extra database processing) + in: query + name: count + schema: + type: string + responses: + "200": + content: + application/json: + schema: + items: + properties: + key: + type: string + type: object + type: array + description: Success + default: + description: "" + /namespaces/{ns}/tokens/balances: + get: + description: 'TODO: Description' + operationId: getTokenBalances + parameters: - description: 'TODO: Description' in: path name: ns diff --git a/internal/apiserver/route_get_token_accounts.go b/internal/apiserver/route_get_token_accounts.go index 3e43229ced..dec93a3f58 100644 --- a/internal/apiserver/route_get_token_accounts.go +++ b/internal/apiserver/route_get_token_accounts.go @@ -34,7 +34,7 @@ var getTokenAccounts = &oapispec.Route{ {Name: "ns", ExampleFromConf: config.NamespacesDefault, Description: i18n.MsgTBD}, }, QueryParams: nil, - FilterFactory: database.TokenAccountQueryFactory, + FilterFactory: database.TokenBalanceQueryFactory, Description: i18n.MsgTBD, JSONInputValue: nil, JSONOutputValue: func() interface{} { return []*fftypes.TokenAccount{} }, diff --git a/internal/apiserver/route_get_token_accounts_by_pool.go b/internal/apiserver/route_get_token_accounts_by_pool.go index 26eaa348e2..5e7f602202 100644 --- a/internal/apiserver/route_get_token_accounts_by_pool.go +++ b/internal/apiserver/route_get_token_accounts_by_pool.go @@ -36,12 +36,13 @@ var getTokenAccountsByPool = &oapispec.Route{ {Name: "name", Description: i18n.MsgTBD}, }, QueryParams: nil, - FilterFactory: database.TokenAccountQueryFactory, + FilterFactory: database.TokenBalanceQueryFactory, Description: i18n.MsgTBD, JSONInputValue: nil, - JSONOutputValue: func() interface{} { return []*fftypes.TokenAccount{} }, + JSONOutputValue: func() interface{} { return []*fftypes.TokenBalance{} }, JSONOutputCodes: []int{http.StatusOK}, JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { - return filterResult(r.Or.Assets().GetTokenAccountsByPool(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Filter)) + return filterResult(r.Or.Assets().GetTokenBalancesByPool(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Filter)) }, + Deprecated: true, } diff --git a/internal/apiserver/route_get_token_accounts_by_pool_test.go b/internal/apiserver/route_get_token_accounts_by_pool_test.go index faa90d45c5..88e0081b96 100644 --- a/internal/apiserver/route_get_token_accounts_by_pool_test.go +++ b/internal/apiserver/route_get_token_accounts_by_pool_test.go @@ -34,8 +34,8 @@ func TestGetTokenAccountsByPool(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mam.On("GetTokenAccountsByPool", mock.Anything, "ns1", "tok1", "pool1", mock.Anything). - Return([]*fftypes.TokenAccount{}, nil, nil) + mam.On("GetTokenBalancesByPool", mock.Anything, "ns1", "tok1", "pool1", mock.Anything). + Return([]*fftypes.TokenBalance{}, nil, nil) r.ServeHTTP(res, req) assert.Equal(t, 200, res.Result().StatusCode) diff --git a/internal/apiserver/route_get_token_balances.go b/internal/apiserver/route_get_token_balances.go new file mode 100644 index 0000000000..2eb7a1f218 --- /dev/null +++ b/internal/apiserver/route_get_token_balances.go @@ -0,0 +1,45 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apiserver + +import ( + "net/http" + + "github.com/hyperledger/firefly/internal/config" + "github.com/hyperledger/firefly/internal/i18n" + "github.com/hyperledger/firefly/internal/oapispec" + "github.com/hyperledger/firefly/pkg/database" + "github.com/hyperledger/firefly/pkg/fftypes" +) + +var getTokenBalances = &oapispec.Route{ + Name: "getTokenBalances", + Path: "namespaces/{ns}/tokens/balances", + Method: http.MethodGet, + PathParams: []*oapispec.PathParam{ + {Name: "ns", ExampleFromConf: config.NamespacesDefault, Description: i18n.MsgTBD}, + }, + QueryParams: nil, + FilterFactory: database.TokenBalanceQueryFactory, + Description: i18n.MsgTBD, + JSONInputValue: nil, + JSONOutputValue: func() interface{} { return []*fftypes.TokenBalance{} }, + JSONOutputCodes: []int{http.StatusOK}, + JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { + return filterResult(r.Or.Assets().GetTokenBalances(r.Ctx, r.PP["ns"], r.Filter)) + }, +} diff --git a/internal/apiserver/route_get_token_balances_test.go b/internal/apiserver/route_get_token_balances_test.go new file mode 100644 index 0000000000..d25168acc4 --- /dev/null +++ b/internal/apiserver/route_get_token_balances_test.go @@ -0,0 +1,42 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apiserver + +import ( + "net/http/httptest" + "testing" + + "github.com/hyperledger/firefly/mocks/assetmocks" + "github.com/hyperledger/firefly/pkg/fftypes" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestGetTokenBalances(t *testing.T) { + o, r := newTestAPIServer() + mam := &assetmocks.Manager{} + o.On("Assets").Return(mam) + req := httptest.NewRequest("GET", "/api/v1/namespaces/ns1/tokens/balances", nil) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + mam.On("GetTokenBalances", mock.Anything, "ns1", mock.Anything). + Return([]*fftypes.TokenBalance{}, nil, nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 200, res.Result().StatusCode) +} diff --git a/internal/apiserver/route_get_token_pool_by_name.go b/internal/apiserver/route_get_token_pool_by_name.go index aeef91f802..a747ecb36c 100644 --- a/internal/apiserver/route_get_token_pool_by_name.go +++ b/internal/apiserver/route_get_token_pool_by_name.go @@ -44,4 +44,5 @@ var getTokenPoolByName = &oapispec.Route{ output, err = r.Or.Assets().GetTokenPool(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"]) return output, err }, + Deprecated: true, } diff --git a/internal/apiserver/route_get_token_pools_by_type.go b/internal/apiserver/route_get_token_pools_by_type.go index 22831fcf1d..91ab4ac92a 100644 --- a/internal/apiserver/route_get_token_pools_by_type.go +++ b/internal/apiserver/route_get_token_pools_by_type.go @@ -43,4 +43,5 @@ var getTokenPoolsByType = &oapispec.Route{ JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { return filterResult(r.Or.Assets().GetTokenPoolsByType(r.Ctx, r.PP["ns"], r.PP["type"], r.Filter)) }, + Deprecated: true, } diff --git a/internal/apiserver/route_get_token_transfers_by_pool.go b/internal/apiserver/route_get_token_transfers_by_pool.go index 5eb57e2806..e589b40baa 100644 --- a/internal/apiserver/route_get_token_transfers_by_pool.go +++ b/internal/apiserver/route_get_token_transfers_by_pool.go @@ -44,4 +44,5 @@ var getTokenTransfersByPool = &oapispec.Route{ JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { return filterResult(r.Or.Assets().GetTokenTransfersByPool(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Filter)) }, + Deprecated: true, } diff --git a/internal/apiserver/route_post_token_burn_by_type.go b/internal/apiserver/route_post_token_burn_by_type.go index 36de79899a..f1d0ba9177 100644 --- a/internal/apiserver/route_post_token_burn_by_type.go +++ b/internal/apiserver/route_post_token_burn_by_type.go @@ -49,4 +49,5 @@ var postTokenBurnByType = &oapispec.Route{ r.SuccessStatus = syncRetcode(waitConfirm) return r.Or.Assets().BurnTokensByType(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) }, + Deprecated: true, } diff --git a/internal/apiserver/route_post_token_mint_by_type.go b/internal/apiserver/route_post_token_mint_by_type.go index 440366d3cc..1e87b71216 100644 --- a/internal/apiserver/route_post_token_mint_by_type.go +++ b/internal/apiserver/route_post_token_mint_by_type.go @@ -49,4 +49,5 @@ var postTokenMintByType = &oapispec.Route{ r.SuccessStatus = syncRetcode(waitConfirm) return r.Or.Assets().MintTokensByType(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) }, + Deprecated: true, } diff --git a/internal/apiserver/route_post_token_pool_by_type.go b/internal/apiserver/route_post_token_pool_by_type.go index 21cdd84a55..82d0a82c52 100644 --- a/internal/apiserver/route_post_token_pool_by_type.go +++ b/internal/apiserver/route_post_token_pool_by_type.go @@ -48,4 +48,5 @@ var postTokenPoolByType = &oapispec.Route{ r.SuccessStatus = syncRetcode(waitConfirm) return r.Or.Assets().CreateTokenPoolByType(r.Ctx, r.PP["ns"], r.PP["type"], r.Input.(*fftypes.TokenPool), waitConfirm) }, + Deprecated: true, } diff --git a/internal/apiserver/route_post_token_transfer_by_type.go b/internal/apiserver/route_post_token_transfer_by_type.go index c838f0b91f..e37439cc8b 100644 --- a/internal/apiserver/route_post_token_transfer_by_type.go +++ b/internal/apiserver/route_post_token_transfer_by_type.go @@ -49,4 +49,5 @@ var postTokenTransferByType = &oapispec.Route{ r.SuccessStatus = syncRetcode(waitConfirm) return r.Or.Assets().TransferTokensByType(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) }, + Deprecated: true, } diff --git a/internal/apiserver/routes.go b/internal/apiserver/routes.go index 1fc28b3f2e..80314345c9 100644 --- a/internal/apiserver/routes.go +++ b/internal/apiserver/routes.go @@ -83,6 +83,7 @@ var routes = []*oapispec.Route{ getTokenPoolsByType, getTokenPoolByNameOrID, getTokenPoolByName, + getTokenBalances, getTokenAccounts, getTokenAccountsByPool, getTokenTransfers, diff --git a/internal/assets/manager.go b/internal/assets/manager.go index 5cd1c85639..c1f2b07c64 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -36,29 +36,36 @@ import ( type Manager interface { CreateTokenPool(ctx context.Context, ns string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) - CreateTokenPoolByType(ctx context.Context, ns, connector string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) GetTokenPools(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenPool, *database.FilterResult, error) - GetTokenPoolsByType(ctx context.Context, ns, connector string, filter database.AndFilter) ([]*fftypes.TokenPool, *database.FilterResult, error) GetTokenPool(ctx context.Context, ns, connector, poolName string) (*fftypes.TokenPool, error) GetTokenPoolByNameOrID(ctx context.Context, ns string, poolNameOrID string) (*fftypes.TokenPool, error) ValidateTokenPoolTx(ctx context.Context, pool *fftypes.TokenPool, protocolTxID string) error + + GetTokenBalances(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenBalance, *database.FilterResult, error) GetTokenAccounts(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenAccount, *database.FilterResult, error) - GetTokenAccountsByPool(ctx context.Context, ns, connector, poolName string, filter database.AndFilter) ([]*fftypes.TokenAccount, *database.FilterResult, error) + GetTokenTransfers(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenTransfer, *database.FilterResult, error) GetTokenTransferByID(ctx context.Context, ns, id string) (*fftypes.TokenTransfer, error) - GetTokenTransfersByPool(ctx context.Context, ns, connector, poolName string, filter database.AndFilter) ([]*fftypes.TokenTransfer, *database.FilterResult, error) + NewTransfer(ns, connector, poolName string, transfer *fftypes.TokenTransferInput) sysmessaging.MessageSender MintTokens(ctx context.Context, ns string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) - MintTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) BurnTokens(ctx context.Context, ns string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) - BurnTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) TransferTokens(ctx context.Context, ns string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) - TransferTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) + GetTokenConnectors(ctx context.Context, ns string) ([]*fftypes.TokenConnector, error) // Bound token callbacks TokenPoolCreated(ti tokens.Plugin, pool *fftypes.TokenPool, protocolTxID string, additionalInfo fftypes.JSONObject) error + // Deprecated + CreateTokenPoolByType(ctx context.Context, ns, connector string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) + GetTokenPoolsByType(ctx context.Context, ns, connector string, filter database.AndFilter) ([]*fftypes.TokenPool, *database.FilterResult, error) + GetTokenBalancesByPool(ctx context.Context, ns, connector, poolName string, filter database.AndFilter) ([]*fftypes.TokenBalance, *database.FilterResult, error) + GetTokenTransfersByPool(ctx context.Context, ns, connector, poolName string, filter database.AndFilter) ([]*fftypes.TokenTransfer, *database.FilterResult, error) + MintTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) + BurnTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) + TransferTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) + Start() error WaitStop() } @@ -112,16 +119,20 @@ func (am *assetManager) scopeNS(ns string, filter database.AndFilter) database.A return filter.Condition(filter.Builder().Eq("namespace", ns)) } -func (am *assetManager) GetTokenAccounts(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenAccount, *database.FilterResult, error) { - return am.database.GetTokenAccounts(ctx, am.scopeNS(ns, filter)) +func (am *assetManager) GetTokenBalances(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenBalance, *database.FilterResult, error) { + return am.database.GetTokenBalances(ctx, am.scopeNS(ns, filter)) } -func (am *assetManager) GetTokenAccountsByPool(ctx context.Context, ns, connector, poolName string, filter database.AndFilter) ([]*fftypes.TokenAccount, *database.FilterResult, error) { +func (am *assetManager) GetTokenBalancesByPool(ctx context.Context, ns, connector, poolName string, filter database.AndFilter) ([]*fftypes.TokenBalance, *database.FilterResult, error) { pool, err := am.GetTokenPool(ctx, ns, connector, poolName) if err != nil { return nil, nil, err } - return am.database.GetTokenAccounts(ctx, filter.Condition(filter.Builder().Eq("poolprotocolid", pool.ProtocolID))) + return am.database.GetTokenBalances(ctx, filter.Condition(filter.Builder().Eq("poolprotocolid", pool.ProtocolID))) +} + +func (am *assetManager) GetTokenAccounts(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenAccount, *database.FilterResult, error) { + return am.database.GetTokenAccounts(ctx, am.scopeNS(ns, filter)) } func (am *assetManager) GetTokenConnectors(ctx context.Context, ns string) ([]*fftypes.TokenConnector, error) { diff --git a/internal/assets/manager_test.go b/internal/assets/manager_test.go index 0bab15dd7d..0dc79c0d58 100644 --- a/internal/assets/manager_test.go +++ b/internal/assets/manager_test.go @@ -67,19 +67,19 @@ func TestStartStop(t *testing.T) { am.WaitStop() } -func TestGetTokenAccounts(t *testing.T) { +func TestGetTokenBalances(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() mdi := am.database.(*databasemocks.Plugin) - fb := database.TokenAccountQueryFactory.NewFilter(context.Background()) + fb := database.TokenBalanceQueryFactory.NewFilter(context.Background()) f := fb.And() - mdi.On("GetTokenAccounts", context.Background(), f).Return([]*fftypes.TokenAccount{}, nil, nil) - _, _, err := am.GetTokenAccounts(context.Background(), "ns1", f) + mdi.On("GetTokenBalances", context.Background(), f).Return([]*fftypes.TokenBalance{}, nil, nil) + _, _, err := am.GetTokenBalances(context.Background(), "ns1", f) assert.NoError(t, err) } -func TestGetTokenAccountsByPool(t *testing.T) { +func TestGetTokenBalancesByPool(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -87,26 +87,38 @@ func TestGetTokenAccountsByPool(t *testing.T) { ID: fftypes.NewUUID(), } mdi := am.database.(*databasemocks.Plugin) - fb := database.TokenAccountQueryFactory.NewFilter(context.Background()) + fb := database.TokenBalanceQueryFactory.NewFilter(context.Background()) f := fb.And() mdi.On("GetTokenPool", context.Background(), "ns1", "test").Return(pool, nil) - mdi.On("GetTokenAccounts", context.Background(), f).Return([]*fftypes.TokenAccount{}, nil, nil) - _, _, err := am.GetTokenAccountsByPool(context.Background(), "ns1", "magic-tokens", "test", f) + mdi.On("GetTokenBalances", context.Background(), f).Return([]*fftypes.TokenBalance{}, nil, nil) + _, _, err := am.GetTokenBalancesByPool(context.Background(), "ns1", "magic-tokens", "test", f) assert.NoError(t, err) } -func TestGetTokenAccountsByPoolBadPool(t *testing.T) { +func TestGetTokenBalancesByPoolBadPool(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() mdi := am.database.(*databasemocks.Plugin) - fb := database.TokenAccountQueryFactory.NewFilter(context.Background()) + fb := database.TokenBalanceQueryFactory.NewFilter(context.Background()) f := fb.And() mdi.On("GetTokenPool", context.Background(), "ns1", "test").Return(nil, fmt.Errorf("pop")) - _, _, err := am.GetTokenAccountsByPool(context.Background(), "ns1", "magic-tokens", "test", f) + _, _, err := am.GetTokenBalancesByPool(context.Background(), "ns1", "magic-tokens", "test", f) assert.EqualError(t, err, "pop") } +func TestGetTokenAccounts(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mdi := am.database.(*databasemocks.Plugin) + fb := database.TokenBalanceQueryFactory.NewFilter(context.Background()) + f := fb.And() + mdi.On("GetTokenAccounts", context.Background(), f).Return([]*fftypes.TokenAccount{}, nil, nil) + _, _, err := am.GetTokenAccounts(context.Background(), "ns1", f) + assert.NoError(t, err) +} + func TestGetTokenConnectors(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() diff --git a/internal/database/sqlcommon/tokenaccount_sql.go b/internal/database/sqlcommon/tokenaccount_sql.go deleted file mode 100644 index e7fffdebaf..0000000000 --- a/internal/database/sqlcommon/tokenaccount_sql.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package sqlcommon - -import ( - "context" - "database/sql" - - sq "github.com/Masterminds/squirrel" - "github.com/hyperledger/firefly/internal/i18n" - "github.com/hyperledger/firefly/internal/log" - "github.com/hyperledger/firefly/pkg/database" - "github.com/hyperledger/firefly/pkg/fftypes" -) - -var ( - tokenAccountColumns = []string{ - "pool_protocol_id", - "token_index", - "connector", - "namespace", - "key", - "balance", - "updated", - } - tokenAccountFilterFieldMap = map[string]string{ - "poolprotocolid": "pool_protocol_id", - "tokenindex": "token_index", - } -) - -func (s *SQLCommon) AddTokenAccountBalance(ctx context.Context, account *fftypes.TokenBalanceChange) (err error) { - ctx, tx, autoCommit, err := s.beginOrUseTx(ctx) - if err != nil { - return err - } - defer s.rollbackTx(ctx, tx, autoCommit) - - rows, _, err := s.queryTx(ctx, tx, - sq.Select("balance"). - From("tokenaccount"). - Where(sq.And{ - sq.Eq{"pool_protocol_id": account.PoolProtocolID}, - sq.Eq{"token_index": account.TokenIndex}, - sq.Eq{"key": account.Key}, - }), - ) - if err != nil { - return err - } - existing := rows.Next() - - var balance fftypes.BigInt - if existing { - if err = rows.Scan(&balance); err != nil { - return i18n.WrapError(ctx, err, i18n.MsgDBReadErr, "tokenaccount") - } - } - balance.Int().Add(balance.Int(), account.Amount.Int()) - rows.Close() - - if existing { - if err = s.updateTx(ctx, tx, - sq.Update("tokenaccount"). - Set("balance", balance). - Set("updated", fftypes.Now()). - Where(sq.And{ - sq.Eq{"pool_protocol_id": account.PoolProtocolID}, - sq.Eq{"token_index": account.TokenIndex}, - sq.Eq{"key": account.Key}, - }), - nil, - ); err != nil { - return err - } - } else { - if _, err = s.insertTx(ctx, tx, - sq.Insert("tokenaccount"). - Columns(tokenAccountColumns...). - Values( - account.PoolProtocolID, - account.TokenIndex, - account.Connector, - account.Namespace, - account.Key, - account.Amount, - fftypes.Now(), - ), - nil, - ); err != nil { - return err - } - } - - return s.commitTx(ctx, tx, autoCommit) -} - -func (s *SQLCommon) tokenAccountResult(ctx context.Context, row *sql.Rows) (*fftypes.TokenAccount, error) { - account := fftypes.TokenAccount{} - err := row.Scan( - &account.PoolProtocolID, - &account.TokenIndex, - &account.Connector, - &account.Namespace, - &account.Key, - &account.Balance, - &account.Updated, - ) - if err != nil { - return nil, i18n.WrapError(ctx, err, i18n.MsgDBReadErr, "tokenaccount") - } - return &account, nil -} - -func (s *SQLCommon) getTokenAccountPred(ctx context.Context, desc string, pred interface{}) (*fftypes.TokenAccount, error) { - rows, _, err := s.query(ctx, - sq.Select(tokenAccountColumns...). - From("tokenaccount"). - Where(pred), - ) - if err != nil { - return nil, err - } - defer rows.Close() - - if !rows.Next() { - log.L(ctx).Debugf("Token account '%s' not found", desc) - return nil, nil - } - - account, err := s.tokenAccountResult(ctx, rows) - if err != nil { - return nil, err - } - - return account, nil -} - -func (s *SQLCommon) GetTokenAccount(ctx context.Context, protocolID, tokenIndex, key string) (message *fftypes.TokenAccount, err error) { - desc := fftypes.TokenAccountIdentifier(protocolID, tokenIndex, key) - return s.getTokenAccountPred(ctx, desc, sq.And{ - sq.Eq{"pool_protocol_id": protocolID}, - sq.Eq{"token_index": tokenIndex}, - sq.Eq{"key": key}, - }) -} - -func (s *SQLCommon) GetTokenAccounts(ctx context.Context, filter database.Filter) ([]*fftypes.TokenAccount, *database.FilterResult, error) { - query, fop, fi, err := s.filterSelect(ctx, "", sq.Select(tokenAccountColumns...).From("tokenaccount"), filter, tokenAccountFilterFieldMap, []interface{}{"seq"}) - if err != nil { - return nil, nil, err - } - - rows, tx, err := s.query(ctx, query) - if err != nil { - return nil, nil, err - } - defer rows.Close() - - accounts := []*fftypes.TokenAccount{} - for rows.Next() { - d, err := s.tokenAccountResult(ctx, rows) - if err != nil { - return nil, nil, err - } - accounts = append(accounts, d) - } - - return accounts, s.queryRes(ctx, tx, "tokenaccount", fop, fi), err -} diff --git a/internal/database/sqlcommon/tokenaccount_sql_test.go b/internal/database/sqlcommon/tokenaccount_sql_test.go deleted file mode 100644 index e348237408..0000000000 --- a/internal/database/sqlcommon/tokenaccount_sql_test.go +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright © 2021 Kaleido, Inc. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package sqlcommon - -import ( - "context" - "encoding/json" - "fmt" - "testing" - - "github.com/DATA-DOG/go-sqlmock" - "github.com/hyperledger/firefly/mocks/databasemocks" - "github.com/hyperledger/firefly/pkg/database" - "github.com/hyperledger/firefly/pkg/fftypes" - "github.com/stretchr/testify/assert" -) - -func TestTokenAccountE2EWithDB(t *testing.T) { - - s, cleanup := newSQLiteTestProvider(t) - defer cleanup() - ctx := context.Background() - - // Create a new token account - operation := &fftypes.TokenBalanceChange{ - PoolProtocolID: "F1", - TokenIndex: "1", - Connector: "erc1155", - Namespace: "ns1", - Key: "0x0", - } - operation.Amount.Int().SetInt64(10) - account := &fftypes.TokenAccount{ - PoolProtocolID: "F1", - TokenIndex: "1", - Connector: "erc1155", - Namespace: "ns1", - Key: "0x0", - } - account.Balance.Int().SetInt64(10) - accountJson, _ := json.Marshal(&account) - - err := s.AddTokenAccountBalance(ctx, operation) - assert.NoError(t, err) - - // Query back the token account (by pool ID and identity) - accountRead, err := s.GetTokenAccount(ctx, "F1", "1", "0x0") - assert.NoError(t, err) - assert.NotNil(t, accountRead) - assert.Greater(t, accountRead.Updated.UnixNano(), int64(0)) - accountRead.Updated = nil - accountReadJson, _ := json.Marshal(&accountRead) - assert.Equal(t, string(accountJson), string(accountReadJson)) - - // Query back the token account (by query filter) - fb := database.TokenAccountQueryFactory.NewFilter(ctx) - filter := fb.And( - fb.Eq("poolprotocolid", account.PoolProtocolID), - fb.Eq("tokenindex", account.TokenIndex), - fb.Eq("key", account.Key), - ) - accounts, res, err := s.GetTokenAccounts(ctx, filter.Count(true)) - assert.NoError(t, err) - assert.Equal(t, 1, len(accounts)) - assert.Equal(t, int64(1), *res.TotalCount) - assert.Greater(t, accounts[0].Updated.UnixNano(), int64(0)) - accounts[0].Updated = nil - accountReadJson, _ = json.Marshal(accounts[0]) - assert.Equal(t, string(accountJson), string(accountReadJson)) - - // Add to the balance - err = s.AddTokenAccountBalance(ctx, operation) - assert.NoError(t, err) - - // Query back the token account (by pool ID and identity) - accountRead, err = s.GetTokenAccount(ctx, "F1", "1", "0x0") - assert.NoError(t, err) - assert.NotNil(t, accountRead) - assert.Greater(t, accountRead.Updated.UnixNano(), int64(0)) - accountRead.Updated = nil - accountReadJson, _ = json.Marshal(&accountRead) - account.Balance.Int().SetInt64(20) - accountJson, _ = json.Marshal(&account) - assert.Equal(t, string(accountJson), string(accountReadJson)) -} - -func TestAddTokenAccountBalanceFailBegin(t *testing.T) { - s, mock := newMockProvider().init() - mock.ExpectBegin().WillReturnError(fmt.Errorf("pop")) - err := s.AddTokenAccountBalance(context.Background(), &fftypes.TokenBalanceChange{}) - assert.Regexp(t, "FF10114", err) - assert.NoError(t, mock.ExpectationsWereMet()) -} - -func TestAddTokenAccountBalanceFailSelect(t *testing.T) { - s, mock := newMockProvider().init() - mock.ExpectBegin() - mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) - err := s.AddTokenAccountBalance(context.Background(), &fftypes.TokenBalanceChange{}) - assert.Regexp(t, "FF10115", err) - assert.NoError(t, mock.ExpectationsWereMet()) -} - -func TestAddTokenAccountSelectBadExistingValue(t *testing.T) { - s, mock := newMockProvider().init() - mock.ExpectBegin() - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{ - "balance", - }).AddRow( - "!not an integer", - )) - err := s.AddTokenAccountBalance(context.Background(), &fftypes.TokenBalanceChange{}) - assert.Regexp(t, "FF10121", err) - assert.NoError(t, mock.ExpectationsWereMet()) -} - -func TestAddTokenAccountBalanceFailInsert(t *testing.T) { - s, mock := newMockProvider().init() - mock.ExpectBegin() - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{})) - mock.ExpectExec("INSERT .*").WillReturnError(fmt.Errorf("pop")) - mock.ExpectRollback() - err := s.AddTokenAccountBalance(context.Background(), &fftypes.TokenBalanceChange{}) - assert.Regexp(t, "FF10116", err) - assert.NoError(t, mock.ExpectationsWereMet()) -} - -func TestAddTokenAccountBalanceFailUpdate(t *testing.T) { - s, mock := newMockProvider().init() - mock.ExpectBegin() - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("1")) - mock.ExpectExec("UPDATE .*").WillReturnError(fmt.Errorf("pop")) - mock.ExpectRollback() - err := s.AddTokenAccountBalance(context.Background(), &fftypes.TokenBalanceChange{}) - assert.Regexp(t, "FF10117", err) - assert.NoError(t, mock.ExpectationsWereMet()) -} - -func TestAddTokenAccountBalanceFailCommit(t *testing.T) { - s, mock := newMockProvider().init() - mock.ExpectBegin() - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"})) - mock.ExpectExec("INSERT .*").WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit().WillReturnError(fmt.Errorf("pop")) - err := s.AddTokenAccountBalance(context.Background(), &fftypes.TokenBalanceChange{}) - assert.Regexp(t, "FF10119", err) - assert.NoError(t, mock.ExpectationsWereMet()) -} - -func TestAddTokenAccountBalanceInsertSuccess(t *testing.T) { - s, db := newMockProvider().init() - callbacks := &databasemocks.Callbacks{} - s.SQLCommon.callbacks = callbacks - operation := &fftypes.TokenBalanceChange{ - PoolProtocolID: "F1", - TokenIndex: "1", - Connector: "erc1155", - Namespace: "ns1", - Key: "0x0", - } - operation.Amount.Int().SetInt64(10) - - db.ExpectBegin() - db.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"})) - db.ExpectExec("INSERT .*"). - WithArgs("F1", "1", "erc1155", "ns1", "0x0", sqlmock.AnyArg(), sqlmock.AnyArg()). - WillReturnResult(sqlmock.NewResult(1, 1)) - db.ExpectCommit() - err := s.AddTokenAccountBalance(context.Background(), operation) - assert.NoError(t, err) - assert.NoError(t, db.ExpectationsWereMet()) -} - -func TestAddTokenAccountBalanceUpdateSuccess(t *testing.T) { - s, db := newMockProvider().init() - callbacks := &databasemocks.Callbacks{} - s.SQLCommon.callbacks = callbacks - operation := &fftypes.TokenBalanceChange{ - PoolProtocolID: "F1", - TokenIndex: "1", - Key: "0x0", - } - operation.Amount.Int().SetInt64(10) - - db.ExpectBegin() - db.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"seq"}).AddRow("1")) - db.ExpectExec("UPDATE .*").WillReturnResult(sqlmock.NewResult(1, 1)) - db.ExpectCommit() - err := s.AddTokenAccountBalance(context.Background(), operation) - assert.NoError(t, err) - assert.NoError(t, db.ExpectationsWereMet()) -} - -func TestGetTokenAccountSelectFail(t *testing.T) { - s, mock := newMockProvider().init() - mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) - _, err := s.GetTokenAccount(context.Background(), "F1", "1", "0x0") - assert.Regexp(t, "FF10115", err) - assert.NoError(t, mock.ExpectationsWereMet()) -} - -func TestGetTokenAccountNotFound(t *testing.T) { - s, mock := newMockProvider().init() - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"})) - msg, err := s.GetTokenAccount(context.Background(), "F1", "1", "0x0") - assert.NoError(t, err) - assert.Nil(t, msg) - assert.NoError(t, mock.ExpectationsWereMet()) -} - -func TestGetTokenAccountScanFail(t *testing.T) { - s, mock := newMockProvider().init() - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("only one")) - _, err := s.GetTokenAccount(context.Background(), "F1", "1", "0x0") - assert.Regexp(t, "FF10121", err) - assert.NoError(t, mock.ExpectationsWereMet()) -} - -func TestGetTokenAccountsQueryFail(t *testing.T) { - s, mock := newMockProvider().init() - mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) - f := database.TokenAccountQueryFactory.NewFilter(context.Background()).Eq("poolprotocolid", "") - _, _, err := s.GetTokenAccounts(context.Background(), f) - assert.Regexp(t, "FF10115", err) - assert.NoError(t, mock.ExpectationsWereMet()) -} - -func TestGetTokenAccountsBuildQueryFail(t *testing.T) { - s, _ := newMockProvider().init() - f := database.TokenAccountQueryFactory.NewFilter(context.Background()).Eq("poolprotocolid", map[bool]bool{true: false}) - _, _, err := s.GetTokenAccounts(context.Background(), f) - assert.Regexp(t, "FF10149.*id", err) -} - -func TestGetTokenAccountsScanFail(t *testing.T) { - s, mock := newMockProvider().init() - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"poolprotocolid"}).AddRow("only one")) - f := database.TokenAccountQueryFactory.NewFilter(context.Background()).Eq("poolprotocolid", "") - _, _, err := s.GetTokenAccounts(context.Background(), f) - assert.Regexp(t, "FF10121", err) - assert.NoError(t, mock.ExpectationsWereMet()) -} diff --git a/internal/database/sqlcommon/tokenbalance_sql.go b/internal/database/sqlcommon/tokenbalance_sql.go new file mode 100644 index 0000000000..457b8b642f --- /dev/null +++ b/internal/database/sqlcommon/tokenbalance_sql.go @@ -0,0 +1,218 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqlcommon + +import ( + "context" + "database/sql" + + sq "github.com/Masterminds/squirrel" + "github.com/hyperledger/firefly/internal/i18n" + "github.com/hyperledger/firefly/internal/log" + "github.com/hyperledger/firefly/pkg/database" + "github.com/hyperledger/firefly/pkg/fftypes" +) + +var ( + tokenBalanceColumns = []string{ + "pool_protocol_id", + "token_index", + "connector", + "namespace", + "key", + "balance", + "updated", + } + tokenBalanceFilterFieldMap = map[string]string{ + "poolprotocolid": "pool_protocol_id", + "tokenindex": "token_index", + } +) + +func (s *SQLCommon) addTokenBalance(ctx context.Context, tx *txWrapper, transfer *fftypes.TokenTransfer, key string, negate bool) error { + account, err := s.GetTokenBalance(ctx, transfer.PoolProtocolID, transfer.TokenIndex, key) + if err != nil { + return err + } + + var balance *fftypes.BigInt + if account != nil { + balance = &account.Balance + } else { + balance = &fftypes.BigInt{} + } + if negate { + balance.Int().Sub(balance.Int(), transfer.Amount.Int()) + } else { + balance.Int().Add(balance.Int(), transfer.Amount.Int()) + } + + if account != nil { + if err = s.updateTx(ctx, tx, + sq.Update("tokenbalance"). + Set("balance", balance). + Set("updated", fftypes.Now()). + Where(sq.And{ + sq.Eq{"pool_protocol_id": account.PoolProtocolID}, + sq.Eq{"token_index": account.TokenIndex}, + sq.Eq{"key": account.Key}, + }), + nil, + ); err != nil { + return err + } + } else { + if _, err = s.insertTx(ctx, tx, + sq.Insert("tokenbalance"). + Columns(tokenBalanceColumns...). + Values( + transfer.PoolProtocolID, + transfer.TokenIndex, + transfer.Connector, + transfer.Namespace, + key, + balance, + fftypes.Now(), + ), + nil, + ); err != nil { + return err + } + } + + return nil +} + +func (s *SQLCommon) UpdateTokenBalances(ctx context.Context, transfer *fftypes.TokenTransfer) (err error) { + ctx, tx, autoCommit, err := s.beginOrUseTx(ctx) + if err != nil { + return err + } + defer s.rollbackTx(ctx, tx, autoCommit) + + if transfer.From != "" { + if err := s.addTokenBalance(ctx, tx, transfer, transfer.From, true); err != nil { + return err + } + } + if transfer.To != "" { + if err := s.addTokenBalance(ctx, tx, transfer, transfer.To, false); err != nil { + return err + } + } + + return s.commitTx(ctx, tx, autoCommit) +} + +func (s *SQLCommon) tokenBalanceResult(ctx context.Context, row *sql.Rows) (*fftypes.TokenBalance, error) { + account := fftypes.TokenBalance{} + err := row.Scan( + &account.PoolProtocolID, + &account.TokenIndex, + &account.Connector, + &account.Namespace, + &account.Key, + &account.Balance, + &account.Updated, + ) + if err != nil { + return nil, i18n.WrapError(ctx, err, i18n.MsgDBReadErr, "tokenbalance") + } + return &account, nil +} + +func (s *SQLCommon) getTokenBalancePred(ctx context.Context, desc string, pred interface{}) (*fftypes.TokenBalance, error) { + rows, _, err := s.query(ctx, + sq.Select(tokenBalanceColumns...). + From("tokenbalance"). + Where(pred), + ) + if err != nil { + return nil, err + } + defer rows.Close() + + if !rows.Next() { + log.L(ctx).Debugf("Token account '%s' not found", desc) + return nil, nil + } + + account, err := s.tokenBalanceResult(ctx, rows) + if err != nil { + return nil, err + } + + return account, nil +} + +func (s *SQLCommon) GetTokenBalance(ctx context.Context, protocolID, tokenIndex, key string) (message *fftypes.TokenBalance, err error) { + desc := fftypes.TokenBalanceIdentifier(protocolID, tokenIndex, key) + return s.getTokenBalancePred(ctx, desc, sq.And{ + sq.Eq{"pool_protocol_id": protocolID}, + sq.Eq{"token_index": tokenIndex}, + sq.Eq{"key": key}, + }) +} + +func (s *SQLCommon) GetTokenBalances(ctx context.Context, filter database.Filter) ([]*fftypes.TokenBalance, *database.FilterResult, error) { + query, fop, fi, err := s.filterSelect(ctx, "", sq.Select(tokenBalanceColumns...).From("tokenbalance"), filter, tokenBalanceFilterFieldMap, []interface{}{"seq"}) + if err != nil { + return nil, nil, err + } + + rows, tx, err := s.query(ctx, query) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + accounts := []*fftypes.TokenBalance{} + for rows.Next() { + d, err := s.tokenBalanceResult(ctx, rows) + if err != nil { + return nil, nil, err + } + accounts = append(accounts, d) + } + + return accounts, s.queryRes(ctx, tx, "tokenbalance", fop, fi), err +} + +func (s *SQLCommon) GetTokenAccounts(ctx context.Context, filter database.Filter) ([]*fftypes.TokenAccount, *database.FilterResult, error) { + query, fop, fi, err := s.filterSelect(ctx, "", sq.Select("key").Distinct().From("tokenbalance"), filter, tokenBalanceFilterFieldMap, []interface{}{"seq"}) + if err != nil { + return nil, nil, err + } + + rows, tx, err := s.query(ctx, query) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + var accounts []*fftypes.TokenAccount + for rows.Next() { + var account fftypes.TokenAccount + err := rows.Scan(&account.Key) + if err != nil { + return nil, nil, i18n.WrapError(ctx, err, i18n.MsgDBReadErr, "tokenbalance") + } + accounts = append(accounts, &account) + } + + return accounts, s.queryRes(ctx, tx, "tokenbalance", fop, fi), err +} diff --git a/internal/database/sqlcommon/tokenbalance_sql_test.go b/internal/database/sqlcommon/tokenbalance_sql_test.go new file mode 100644 index 0000000000..6e433ec7e5 --- /dev/null +++ b/internal/database/sqlcommon/tokenbalance_sql_test.go @@ -0,0 +1,250 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqlcommon + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/hyperledger/firefly/pkg/database" + "github.com/hyperledger/firefly/pkg/fftypes" + "github.com/stretchr/testify/assert" +) + +func TestTokenBalanceE2EWithDB(t *testing.T) { + + s, cleanup := newSQLiteTestProvider(t) + defer cleanup() + ctx := context.Background() + + // Create a new token account + transfer := &fftypes.TokenTransfer{ + PoolProtocolID: "F1", + TokenIndex: "1", + Connector: "erc1155", + Namespace: "ns1", + To: "0x0", + Amount: *fftypes.NewBigInt(10), + } + balance := &fftypes.TokenBalance{ + PoolProtocolID: "F1", + TokenIndex: "1", + Connector: "erc1155", + Namespace: "ns1", + Key: "0x0", + Balance: *fftypes.NewBigInt(10), + } + balanceJson, _ := json.Marshal(&balance) + + err := s.UpdateTokenBalances(ctx, transfer) + assert.NoError(t, err) + + // Query back the token balance (by pool ID and identity) + balanceRead, err := s.GetTokenBalance(ctx, "F1", "1", "0x0") + assert.NoError(t, err) + assert.NotNil(t, balanceRead) + assert.Greater(t, balanceRead.Updated.UnixNano(), int64(0)) + balanceRead.Updated = nil + balanceReadJson, _ := json.Marshal(&balanceRead) + assert.Equal(t, string(balanceJson), string(balanceReadJson)) + + // Query back the token balance (by query filter) + fb := database.TokenBalanceQueryFactory.NewFilter(ctx) + filter := fb.And( + fb.Eq("poolprotocolid", balance.PoolProtocolID), + fb.Eq("tokenindex", balance.TokenIndex), + fb.Eq("key", balance.Key), + ) + balances, res, err := s.GetTokenBalances(ctx, filter.Count(true)) + assert.NoError(t, err) + assert.Equal(t, 1, len(balances)) + assert.Equal(t, int64(1), *res.TotalCount) + assert.Greater(t, balances[0].Updated.UnixNano(), int64(0)) + balances[0].Updated = nil + balanceReadJson, _ = json.Marshal(balances[0]) + assert.Equal(t, string(balanceJson), string(balanceReadJson)) + + // Transfer half to a different address + transfer.From = "0x0" + transfer.To = "0x1" + transfer.Amount = *fftypes.NewBigInt(5) + err = s.UpdateTokenBalances(ctx, transfer) + assert.NoError(t, err) + + // Query back the token balance (by pool ID and identity) + balanceRead, err = s.GetTokenBalance(ctx, "F1", "1", "0x0") + assert.NoError(t, err) + assert.NotNil(t, balanceRead) + assert.Greater(t, balanceRead.Updated.UnixNano(), int64(0)) + balanceRead.Updated = nil + balanceReadJson, _ = json.Marshal(&balanceRead) + balance.Balance = *fftypes.NewBigInt(5) + balanceJson, _ = json.Marshal(&balance) + assert.Equal(t, string(balanceJson), string(balanceReadJson)) + + // Query back the other token balance (by pool ID and identity) + balanceRead, err = s.GetTokenBalance(ctx, "F1", "1", "0x1") + assert.NoError(t, err) + assert.NotNil(t, balanceRead) + assert.Greater(t, balanceRead.Updated.UnixNano(), int64(0)) + balanceRead.Updated = nil + balanceReadJson, _ = json.Marshal(&balanceRead) + balance.Key = "0x1" + balance.Balance = *fftypes.NewBigInt(5) + balanceJson, _ = json.Marshal(&balance) + assert.Equal(t, string(balanceJson), string(balanceReadJson)) + + // Query the list of unique accounts + fb2 := database.TokenBalanceQueryFactory.NewFilter(ctx) + accounts, fr, err := s.GetTokenAccounts(ctx, fb2.And().Count(true)) + assert.NoError(t, err) + assert.Equal(t, int64(2), *fr.TotalCount) + assert.Equal(t, 2, len(accounts)) + assert.Equal(t, "0x1", accounts[0].Key) + assert.Equal(t, "0x0", accounts[1].Key) +} + +func TestUpdateTokenBalancesFailBegin(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectBegin().WillReturnError(fmt.Errorf("pop")) + err := s.UpdateTokenBalances(context.Background(), &fftypes.TokenTransfer{}) + assert.Regexp(t, "FF10114", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestUpdateTokenBalancesFailSelect(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectBegin() + mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) + err := s.UpdateTokenBalances(context.Background(), &fftypes.TokenTransfer{To: "0x0"}) + assert.Regexp(t, "FF10115", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestUpdateTokenBalancesFailInsert(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectBegin() + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{})) + mock.ExpectExec("INSERT .*").WillReturnError(fmt.Errorf("pop")) + mock.ExpectRollback() + err := s.UpdateTokenBalances(context.Background(), &fftypes.TokenTransfer{From: "0x0"}) + assert.Regexp(t, "FF10116", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestUpdateTokenBalancesFailInsert2(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectBegin() + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{})) + mock.ExpectExec("INSERT .*").WillReturnError(fmt.Errorf("pop")) + mock.ExpectRollback() + err := s.UpdateTokenBalances(context.Background(), &fftypes.TokenTransfer{To: "0x0"}) + assert.Regexp(t, "FF10116", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestUpdateTokenBalancesFailUpdate(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectBegin() + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows(tokenBalanceColumns).AddRow("F1", "1", "", "", "0x0", "0", 0)) + mock.ExpectExec("UPDATE .*").WillReturnError(fmt.Errorf("pop")) + mock.ExpectRollback() + err := s.UpdateTokenBalances(context.Background(), &fftypes.TokenTransfer{To: "0x0"}) + assert.Regexp(t, "FF10117", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestUpdateTokenBalancesFailCommit(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectBegin() + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{})) + mock.ExpectExec("INSERT .*").WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectCommit().WillReturnError(fmt.Errorf("pop")) + err := s.UpdateTokenBalances(context.Background(), &fftypes.TokenTransfer{To: "0x0"}) + assert.Regexp(t, "FF10119", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestGetTokenBalanceNotFound(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"})) + msg, err := s.GetTokenBalance(context.Background(), "F1", "1", "0x0") + assert.NoError(t, err) + assert.Nil(t, msg) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestGetTokenBalanceScanFail(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("only one")) + _, err := s.GetTokenBalance(context.Background(), "F1", "1", "0x0") + assert.Regexp(t, "FF10121", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestGetTokenBalancesQueryFail(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) + f := database.TokenBalanceQueryFactory.NewFilter(context.Background()).Eq("poolprotocolid", "") + _, _, err := s.GetTokenBalances(context.Background(), f) + assert.Regexp(t, "FF10115", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestGetTokenBalancesBuildQueryFail(t *testing.T) { + s, _ := newMockProvider().init() + f := database.TokenBalanceQueryFactory.NewFilter(context.Background()).Eq("poolprotocolid", map[bool]bool{true: false}) + _, _, err := s.GetTokenBalances(context.Background(), f) + assert.Regexp(t, "FF10149.*id", err) +} + +func TestGetTokenBalancesScanFail(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"poolprotocolid"}).AddRow("only one")) + f := database.TokenBalanceQueryFactory.NewFilter(context.Background()).Eq("poolprotocolid", "") + _, _, err := s.GetTokenBalances(context.Background(), f) + assert.Regexp(t, "FF10121", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestGetTokenAccountsQueryFail(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) + f := database.TokenBalanceQueryFactory.NewFilter(context.Background()).And() + _, _, err := s.GetTokenAccounts(context.Background(), f) + assert.Regexp(t, "FF10115", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestGetTokenAccountsBuildQueryFail(t *testing.T) { + s, _ := newMockProvider().init() + f := database.TokenBalanceQueryFactory.NewFilter(context.Background()).Eq("poolprotocolid", map[bool]bool{true: false}) + _, _, err := s.GetTokenAccounts(context.Background(), f) + assert.Regexp(t, "FF10149.*id", err) +} + +func TestGetTokenAccountsScanFail(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"key", "bad"}).AddRow("too many", "columns")) + f := database.TokenBalanceQueryFactory.NewFilter(context.Background()).And() + _, _, err := s.GetTokenAccounts(context.Background(), f) + assert.Regexp(t, "FF10121", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} diff --git a/internal/events/tokens_transferred.go b/internal/events/tokens_transferred.go index 963d95b116..19440f2df7 100644 --- a/internal/events/tokens_transferred.go +++ b/internal/events/tokens_transferred.go @@ -112,32 +112,10 @@ func (em *eventManager) TokensTransferred(tk tokens.Plugin, transfer *fftypes.To log.L(ctx).Errorf("Failed to record token transfer '%s': %s", transfer.ProtocolID, err) return err } - - balance := &fftypes.TokenBalanceChange{ - PoolProtocolID: transfer.PoolProtocolID, - TokenIndex: transfer.TokenIndex, - Connector: transfer.Connector, - Namespace: transfer.Namespace, - } - - if transfer.Type != fftypes.TokenTransferTypeMint { - balance.Key = transfer.From - balance.Amount.Int().Neg(transfer.Amount.Int()) - if err := em.database.AddTokenAccountBalance(ctx, balance); err != nil { - log.L(ctx).Errorf("Failed to update account '%s' for token transfer '%s': %s", balance.Key, transfer.ProtocolID, err) - return err - } - } - - if transfer.Type != fftypes.TokenTransferTypeBurn { - balance.Key = transfer.To - balance.Amount.Int().Set(transfer.Amount.Int()) - if err := em.database.AddTokenAccountBalance(ctx, balance); err != nil { - log.L(ctx).Errorf("Failed to update account '%s for token transfer '%s': %s", balance.Key, transfer.ProtocolID, err) - return err - } + if err := em.database.UpdateTokenBalances(ctx, transfer); err != nil { + log.L(ctx).Errorf("Failed to update accounts %s -> %s for token transfer '%s': %s", transfer.From, transfer.To, transfer.ProtocolID, err) + return err } - log.L(ctx).Infof("Token transfer recorded id=%s author=%s", transfer.ProtocolID, transfer.Key) if transfer.MessageHash != nil { diff --git a/internal/events/tokens_transferred_test.go b/internal/events/tokens_transferred_test.go index 3ae2993305..47da805404 100644 --- a/internal/events/tokens_transferred_test.go +++ b/internal/events/tokens_transferred_test.go @@ -43,36 +43,18 @@ func TestTokensTransferredSucceedWithRetries(t *testing.T) { Key: "0x12345", From: "0x1", To: "0x2", + Amount: *fftypes.NewBigInt(1), } - transfer.Amount.Int().SetInt64(1) - fromBalance := &fftypes.TokenBalanceChange{ - PoolProtocolID: "F1", - TokenIndex: "0", - Connector: "erc1155", - Namespace: "ns1", - Key: "0x1", - } - fromBalance.Amount.Int().SetInt64(-1) - toBalance := &fftypes.TokenBalanceChange{ - PoolProtocolID: "F1", - TokenIndex: "0", - Connector: "erc1155", - Namespace: "ns1", - Key: "0x2", - } - toBalance.Amount.Int().SetInt64(1) pool := &fftypes.TokenPool{ Namespace: "ns1", } mdi.On("GetTokenPoolByProtocolID", em.ctx, "F1").Return(nil, fmt.Errorf("pop")).Once() - mdi.On("GetTokenPoolByProtocolID", em.ctx, "F1").Return(pool, nil).Times(4) + mdi.On("GetTokenPoolByProtocolID", em.ctx, "F1").Return(pool, nil).Times(3) mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(fmt.Errorf("pop")).Once() - mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(nil).Times(3) - mdi.On("AddTokenAccountBalance", em.ctx, fromBalance).Return(fmt.Errorf("pop")).Once() - mdi.On("AddTokenAccountBalance", em.ctx, fromBalance).Return(nil).Times(2) - mdi.On("AddTokenAccountBalance", em.ctx, toBalance).Return(fmt.Errorf("pop")).Once() - mdi.On("AddTokenAccountBalance", em.ctx, toBalance).Return(nil).Once() + mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(nil).Times(2) + mdi.On("UpdateTokenBalances", em.ctx, transfer).Return(fmt.Errorf("pop")).Once() + mdi.On("UpdateTokenBalances", em.ctx, transfer).Return(nil).Once() mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(ev *fftypes.Event) bool { return ev.Type == fftypes.EventTypeTransferConfirmed && ev.Reference == transfer.LocalID && ev.Namespace == pool.Namespace })).Return(nil).Once() @@ -99,12 +81,12 @@ func TestTokensTransferredWithTransactionRetries(t *testing.T) { Key: "0x12345", From: "0x1", To: "0x2", + Amount: *fftypes.NewBigInt(1), TX: fftypes.TransactionRef{ ID: fftypes.NewUUID(), Type: fftypes.TransactionTypeTokenTransfer, }, } - transfer.Amount.Int().SetInt64(1) pool := &fftypes.TokenPool{ Namespace: "ns1", } @@ -151,8 +133,8 @@ func TestTokensTransferredAddBalanceIgnore(t *testing.T) { Key: "0x12345", From: "0x1", To: "0x2", + Amount: *fftypes.NewBigInt(1), } - transfer.Amount.Int().SetInt64(1) mdi.On("GetTokenPoolByProtocolID", em.ctx, "F1").Return(nil, nil) @@ -180,24 +162,8 @@ func TestTokensTransferredWithMessageReceived(t *testing.T) { From: "0x1", To: "0x2", MessageHash: fftypes.NewRandB32(), + Amount: *fftypes.NewBigInt(1), } - transfer.Amount.Int().SetInt64(1) - fromBalance := &fftypes.TokenBalanceChange{ - PoolProtocolID: "F1", - TokenIndex: "0", - Connector: "erc1155", - Namespace: "ns1", - Key: "0x1", - } - fromBalance.Amount.Int().SetInt64(-1) - toBalance := &fftypes.TokenBalanceChange{ - PoolProtocolID: "F1", - TokenIndex: "0", - Connector: "erc1155", - Namespace: "ns1", - Key: "0x2", - } - toBalance.Amount.Int().SetInt64(1) pool := &fftypes.TokenPool{ Namespace: "ns1", } @@ -207,8 +173,7 @@ func TestTokensTransferredWithMessageReceived(t *testing.T) { mdi.On("GetTokenPoolByProtocolID", em.ctx, "F1").Return(pool, nil).Times(2) mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(nil).Times(2) - mdi.On("AddTokenAccountBalance", em.ctx, fromBalance).Return(nil).Times(2) - mdi.On("AddTokenAccountBalance", em.ctx, toBalance).Return(nil).Times(2) + mdi.On("UpdateTokenBalances", em.ctx, transfer).Return(nil).Times(2) mdi.On("GetMessages", em.ctx, mock.Anything).Return(nil, nil, fmt.Errorf("pop")).Once() mdi.On("GetMessages", em.ctx, mock.Anything).Return(messages, nil, nil).Once() mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(ev *fftypes.Event) bool { @@ -239,24 +204,8 @@ func TestTokensTransferredWithMessageSend(t *testing.T) { From: "0x1", To: "0x2", MessageHash: fftypes.NewRandB32(), + Amount: *fftypes.NewBigInt(1), } - transfer.Amount.Int().SetInt64(1) - fromBalance := &fftypes.TokenBalanceChange{ - PoolProtocolID: "F1", - TokenIndex: "0", - Connector: "erc1155", - Namespace: "ns1", - Key: "0x1", - } - fromBalance.Amount.Int().SetInt64(-1) - toBalance := &fftypes.TokenBalanceChange{ - PoolProtocolID: "F1", - TokenIndex: "0", - Connector: "erc1155", - Namespace: "ns1", - Key: "0x2", - } - toBalance.Amount.Int().SetInt64(1) pool := &fftypes.TokenPool{ Namespace: "ns1", } @@ -267,8 +216,7 @@ func TestTokensTransferredWithMessageSend(t *testing.T) { mdi.On("GetTokenPoolByProtocolID", em.ctx, "F1").Return(pool, nil).Times(2) mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(nil).Times(2) - mdi.On("AddTokenAccountBalance", em.ctx, fromBalance).Return(nil).Times(2) - mdi.On("AddTokenAccountBalance", em.ctx, toBalance).Return(nil).Times(2) + mdi.On("UpdateTokenBalances", em.ctx, transfer).Return(nil).Times(2) mdi.On("GetMessages", em.ctx, mock.Anything).Return(messages, nil, nil).Times(2) mdi.On("UpsertMessage", em.ctx, mock.Anything, true, false).Return(fmt.Errorf("pop")) mdi.On("UpsertMessage", em.ctx, mock.MatchedBy(func(msg *fftypes.Message) bool { diff --git a/mocks/assetmocks/manager.go b/mocks/assetmocks/manager.go index 3480a6407e..5400991f53 100644 --- a/mocks/assetmocks/manager.go +++ b/mocks/assetmocks/manager.go @@ -144,16 +144,48 @@ func (_m *Manager) GetTokenAccounts(ctx context.Context, ns string, filter datab return r0, r1, r2 } -// GetTokenAccountsByPool provides a mock function with given fields: ctx, ns, connector, poolName, filter -func (_m *Manager) GetTokenAccountsByPool(ctx context.Context, ns string, connector string, poolName string, filter database.AndFilter) ([]*fftypes.TokenAccount, *database.FilterResult, error) { +// GetTokenBalances provides a mock function with given fields: ctx, ns, filter +func (_m *Manager) GetTokenBalances(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenBalance, *database.FilterResult, error) { + ret := _m.Called(ctx, ns, filter) + + var r0 []*fftypes.TokenBalance + if rf, ok := ret.Get(0).(func(context.Context, string, database.AndFilter) []*fftypes.TokenBalance); ok { + r0 = rf(ctx, ns, filter) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*fftypes.TokenBalance) + } + } + + var r1 *database.FilterResult + if rf, ok := ret.Get(1).(func(context.Context, string, database.AndFilter) *database.FilterResult); ok { + r1 = rf(ctx, ns, filter) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*database.FilterResult) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(context.Context, string, database.AndFilter) error); ok { + r2 = rf(ctx, ns, filter) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// GetTokenBalancesByPool provides a mock function with given fields: ctx, ns, connector, poolName, filter +func (_m *Manager) GetTokenBalancesByPool(ctx context.Context, ns string, connector string, poolName string, filter database.AndFilter) ([]*fftypes.TokenBalance, *database.FilterResult, error) { ret := _m.Called(ctx, ns, connector, poolName, filter) - var r0 []*fftypes.TokenAccount - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, database.AndFilter) []*fftypes.TokenAccount); ok { + var r0 []*fftypes.TokenBalance + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, database.AndFilter) []*fftypes.TokenBalance); ok { r0 = rf(ctx, ns, connector, poolName, filter) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]*fftypes.TokenAccount) + r0 = ret.Get(0).([]*fftypes.TokenBalance) } } diff --git a/mocks/databasemocks/plugin.go b/mocks/databasemocks/plugin.go index fd04026b07..1e5dcca5b6 100644 --- a/mocks/databasemocks/plugin.go +++ b/mocks/databasemocks/plugin.go @@ -19,20 +19,6 @@ type Plugin struct { mock.Mock } -// AddTokenAccountBalance provides a mock function with given fields: ctx, account -func (_m *Plugin) AddTokenAccountBalance(ctx context.Context, account *fftypes.TokenBalanceChange) error { - ret := _m.Called(ctx, account) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.TokenBalanceChange) error); ok { - r0 = rf(ctx, account) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // Capabilities provides a mock function with given fields: func (_m *Plugin) Capabilities() *database.Capabilities { ret := _m.Called() @@ -1307,16 +1293,48 @@ func (_m *Plugin) GetSubscriptions(ctx context.Context, filter database.Filter) return r0, r1, r2 } -// GetTokenAccount provides a mock function with given fields: ctx, protocolID, tokenIndex, identity -func (_m *Plugin) GetTokenAccount(ctx context.Context, protocolID string, tokenIndex string, identity string) (*fftypes.TokenAccount, error) { +// GetTokenAccounts provides a mock function with given fields: ctx, filter +func (_m *Plugin) GetTokenAccounts(ctx context.Context, filter database.Filter) ([]*fftypes.TokenAccount, *database.FilterResult, error) { + ret := _m.Called(ctx, filter) + + var r0 []*fftypes.TokenAccount + if rf, ok := ret.Get(0).(func(context.Context, database.Filter) []*fftypes.TokenAccount); ok { + r0 = rf(ctx, filter) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*fftypes.TokenAccount) + } + } + + var r1 *database.FilterResult + if rf, ok := ret.Get(1).(func(context.Context, database.Filter) *database.FilterResult); ok { + r1 = rf(ctx, filter) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*database.FilterResult) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(context.Context, database.Filter) error); ok { + r2 = rf(ctx, filter) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// GetTokenBalance provides a mock function with given fields: ctx, protocolID, tokenIndex, identity +func (_m *Plugin) GetTokenBalance(ctx context.Context, protocolID string, tokenIndex string, identity string) (*fftypes.TokenBalance, error) { ret := _m.Called(ctx, protocolID, tokenIndex, identity) - var r0 *fftypes.TokenAccount - if rf, ok := ret.Get(0).(func(context.Context, string, string, string) *fftypes.TokenAccount); ok { + var r0 *fftypes.TokenBalance + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) *fftypes.TokenBalance); ok { r0 = rf(ctx, protocolID, tokenIndex, identity) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*fftypes.TokenAccount) + r0 = ret.Get(0).(*fftypes.TokenBalance) } } @@ -1330,16 +1348,16 @@ func (_m *Plugin) GetTokenAccount(ctx context.Context, protocolID string, tokenI return r0, r1 } -// GetTokenAccounts provides a mock function with given fields: ctx, filter -func (_m *Plugin) GetTokenAccounts(ctx context.Context, filter database.Filter) ([]*fftypes.TokenAccount, *database.FilterResult, error) { +// GetTokenBalances provides a mock function with given fields: ctx, filter +func (_m *Plugin) GetTokenBalances(ctx context.Context, filter database.Filter) ([]*fftypes.TokenBalance, *database.FilterResult, error) { ret := _m.Called(ctx, filter) - var r0 []*fftypes.TokenAccount - if rf, ok := ret.Get(0).(func(context.Context, database.Filter) []*fftypes.TokenAccount); ok { + var r0 []*fftypes.TokenBalance + if rf, ok := ret.Get(0).(func(context.Context, database.Filter) []*fftypes.TokenBalance); ok { r0 = rf(ctx, filter) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]*fftypes.TokenAccount) + r0 = ret.Get(0).([]*fftypes.TokenBalance) } } @@ -1858,6 +1876,20 @@ func (_m *Plugin) UpdateSubscription(ctx context.Context, ns string, name string return r0 } +// UpdateTokenBalances provides a mock function with given fields: ctx, transfer +func (_m *Plugin) UpdateTokenBalances(ctx context.Context, transfer *fftypes.TokenTransfer) error { + ret := _m.Called(ctx, transfer) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.TokenTransfer) error); ok { + r0 = rf(ctx, transfer) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // UpdateTransaction provides a mock function with given fields: ctx, id, update func (_m *Plugin) UpdateTransaction(ctx context.Context, id *fftypes.UUID, update database.Update) error { ret := _m.Called(ctx, id, update) diff --git a/pkg/database/plugin.go b/pkg/database/plugin.go index f5c979592a..41d2d8378f 100644 --- a/pkg/database/plugin.go +++ b/pkg/database/plugin.go @@ -363,14 +363,17 @@ type iTokenPoolCollection interface { GetTokenPools(ctx context.Context, filter Filter) ([]*fftypes.TokenPool, *FilterResult, error) } -type iTokenAccountCollection interface { - // AddTokenAccountBalance - Add a (positive or negative) balance to the account's current balance - AddTokenAccountBalance(ctx context.Context, account *fftypes.TokenBalanceChange) error +type iTokenBalanceCollection interface { + // UpdateTokenBalances - Move some token balance from one account to another + UpdateTokenBalances(ctx context.Context, transfer *fftypes.TokenTransfer) error - // GetTokenAccount - Get a token account by pool and account identity - GetTokenAccount(ctx context.Context, protocolID, tokenIndex, identity string) (*fftypes.TokenAccount, error) + // GetTokenBalance - Get a token balance by pool and account identity + GetTokenBalance(ctx context.Context, protocolID, tokenIndex, identity string) (*fftypes.TokenBalance, error) - // GetTokenAccounts - Get token accounts + // GetTokenBalances - Get token balances + GetTokenBalances(ctx context.Context, filter Filter) ([]*fftypes.TokenBalance, *FilterResult, error) + + // GetTokenAccounts - Get token accounts (all distinct addresses that have a balance) GetTokenAccounts(ctx context.Context, filter Filter) ([]*fftypes.TokenAccount, *FilterResult, error) } @@ -439,7 +442,7 @@ type PeristenceInterface interface { iBlobCollection iConfigRecordCollection iTokenPoolCollection - iTokenAccountCollection + iTokenBalanceCollection iTokenTransferCollection } @@ -510,7 +513,7 @@ const ( CollectionNextpins OtherCollection = "nextpins" CollectionNonces OtherCollection = "nonces" CollectionOffsets OtherCollection = "offsets" - CollectionTokenAccounts OtherCollection = "tokenaccounts" + CollectionTokenBalances OtherCollection = "tokenbalances" ) // Callbacks are the methods for passing data from plugin to core @@ -766,8 +769,8 @@ var TokenPoolQueryFactory = &queryFields{ "connector": &StringField{}, } -// TokenAccountQueryFactory filter fields for token accounts -var TokenAccountQueryFactory = &queryFields{ +// TokenBalanceQueryFactory filter fields for token accounts +var TokenBalanceQueryFactory = &queryFields{ "poolprotocolid": &StringField{}, "tokenindex": &StringField{}, "connector": &StringField{}, diff --git a/pkg/fftypes/bigint.go b/pkg/fftypes/bigint.go index 1c0c498466..ee0da2abe3 100644 --- a/pkg/fftypes/bigint.go +++ b/pkg/fftypes/bigint.go @@ -57,6 +57,10 @@ func (i *BigInt) UnmarshalJSON(b []byte) error { } } +func NewBigInt(x int64) *BigInt { + return (*BigInt)(big.NewInt(x)) +} + func (i BigInt) Value() (driver.Value, error) { // Represent as base 16 string in database, to allow a 64 character limit res := (*big.Int)(&i).Text(16) diff --git a/pkg/fftypes/bigint_test.go b/pkg/fftypes/bigint_test.go index 7c7eed0eff..900a9eb5a7 100644 --- a/pkg/fftypes/bigint_test.go +++ b/pkg/fftypes/bigint_test.go @@ -209,3 +209,10 @@ func TestEquals(t *testing.T) { assert.True(t, i2.Equals(&i1)) } + +func TestNewBigInt(t *testing.T) { + + n := NewBigInt(10) + assert.Equal(t, int64(10), n.Int().Int64()) + +} diff --git a/pkg/fftypes/tokenaccount.go b/pkg/fftypes/tokenbalance.go similarity index 74% rename from pkg/fftypes/tokenaccount.go rename to pkg/fftypes/tokenbalance.go index 1857fc5805..3be506dfa0 100644 --- a/pkg/fftypes/tokenaccount.go +++ b/pkg/fftypes/tokenbalance.go @@ -16,7 +16,7 @@ package fftypes -type TokenAccount struct { +type TokenBalance struct { PoolProtocolID string `json:"poolProtocolId,omitempty"` TokenIndex string `json:"tokenIndex,omitempty"` Connector string `json:"connector,omitempty"` @@ -26,19 +26,16 @@ type TokenAccount struct { Updated *FFTime `json:"updated,omitempty"` } -func TokenAccountIdentifier(protocolID, tokenIndex, identity string) string { +func TokenBalanceIdentifier(protocolID, tokenIndex, identity string) string { return protocolID + ":" + tokenIndex + ":" + identity } -func (t *TokenAccount) Identifier() string { - return TokenAccountIdentifier(t.PoolProtocolID, t.TokenIndex, t.Key) +func (t *TokenBalance) Identifier() string { + return TokenBalanceIdentifier(t.PoolProtocolID, t.TokenIndex, t.Key) } -type TokenBalanceChange struct { - PoolProtocolID string - TokenIndex string - Connector string - Namespace string - Key string - Amount BigInt +// Currently this type is just a filtered view of TokenBalance. +// If more fields/aggregation become needed, this may need its own table in the database. +type TokenAccount struct { + Key string `json:"key,omitempty"` } diff --git a/pkg/fftypes/tokenaccount_test.go b/pkg/fftypes/tokenbalance_test.go similarity index 86% rename from pkg/fftypes/tokenaccount_test.go rename to pkg/fftypes/tokenbalance_test.go index bf40fa5009..426ed31d8b 100644 --- a/pkg/fftypes/tokenaccount_test.go +++ b/pkg/fftypes/tokenbalance_test.go @@ -22,11 +22,11 @@ import ( "github.com/stretchr/testify/assert" ) -func TestTokenAccountIdentifier(t *testing.T) { - account := &TokenAccount{ +func TestTokenBalanceIdentifier(t *testing.T) { + balance := &TokenBalance{ PoolProtocolID: "123", TokenIndex: "1", Key: "0x00", } - assert.Equal(t, "123:1:0x00", account.Identifier()) + assert.Equal(t, "123:1:0x00", balance.Identifier()) } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index e2c2ddd6e3..cc16b469d1 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -122,9 +122,9 @@ func validateReceivedMessages(ts *testState, client *resty.Client, msgType fftyp return msgData.Value } -func validateAccountBalances(t *testing.T, client *resty.Client, poolName, tokenIndex string, balances map[string]int64) { +func validateAccountBalances(t *testing.T, client *resty.Client, poolProtocolID, tokenIndex string, balances map[string]int64) { for key, balance := range balances { - account := GetTokenAccount(t, client, poolName, tokenIndex, key) + account := GetTokenBalance(t, client, poolProtocolID, tokenIndex, key) assert.Equal(t, "erc1155", account.Connector) assert.Equal(t, balance, account.Balance.Int().Int64()) } diff --git a/test/e2e/restclient.go b/test/e2e/restclient.go index 23c49fb5f2..a4c52f0a92 100644 --- a/test/e2e/restclient.go +++ b/test/e2e/restclient.go @@ -43,12 +43,12 @@ var ( urlGetData = "/namespaces/default/data" urlGetDataBlob = "/namespaces/default/data/%s/blob" urlSubscriptions = "/namespaces/default/subscriptions" - urlTokenPools = "/namespaces/default/tokens/erc1155/pools" urlDatatypes = "/namespaces/default/datatypes" - urlTokenMint = "/namespaces/default/tokens/erc1155/pools/%s/mint" - urlTokenBurn = "/namespaces/default/tokens/erc1155/pools/%s/burn" - urlTokenTransfers = "/namespaces/default/tokens/erc1155/pools/%s/transfers" - urlTokenAccounts = "/namespaces/default/tokens/erc1155/pools/%s/accounts" + urlTokenPools = "/namespaces/default/tokens/pools" + urlTokenMint = "/namespaces/default/tokens/mint" + urlTokenBurn = "/namespaces/default/tokens/burn" + urlTokenTransfers = "/namespaces/default/tokens/transfers" + urlTokenBalances = "/namespaces/default/tokens/balances" urlGetOrganizations = "/network/organizations" ) @@ -368,9 +368,9 @@ func GetTokenPools(t *testing.T, client *resty.Client, startTime time.Time) (poo return pools } -func MintTokens(t *testing.T, client *resty.Client, poolName string, mint *fftypes.TokenTransferInput, confirm bool) *fftypes.TokenTransfer { +func MintTokens(t *testing.T, client *resty.Client, mint *fftypes.TokenTransferInput, confirm bool) *fftypes.TokenTransfer { var transferOut fftypes.TokenTransfer - path := fmt.Sprintf(urlTokenMint, poolName) + path := urlTokenMint resp, err := client.R(). SetBody(mint). SetQueryParam("confirm", strconv.FormatBool(confirm)). @@ -385,9 +385,9 @@ func MintTokens(t *testing.T, client *resty.Client, poolName string, mint *fftyp return &transferOut } -func BurnTokens(t *testing.T, client *resty.Client, poolName string, burn *fftypes.TokenTransferInput, confirm bool) *fftypes.TokenTransfer { +func BurnTokens(t *testing.T, client *resty.Client, burn *fftypes.TokenTransferInput, confirm bool) *fftypes.TokenTransfer { var transferOut fftypes.TokenTransfer - path := fmt.Sprintf(urlTokenBurn, poolName) + path := urlTokenBurn resp, err := client.R(). SetBody(burn). SetQueryParam("confirm", strconv.FormatBool(confirm)). @@ -402,9 +402,9 @@ func BurnTokens(t *testing.T, client *resty.Client, poolName string, burn *fftyp return &transferOut } -func TransferTokens(t *testing.T, client *resty.Client, poolName string, transfer *fftypes.TokenTransferInput, confirm bool) *fftypes.TokenTransfer { +func TransferTokens(t *testing.T, client *resty.Client, transfer *fftypes.TokenTransferInput, confirm bool) *fftypes.TokenTransfer { var transferOut fftypes.TokenTransfer - path := fmt.Sprintf(urlTokenTransfers, poolName) + path := urlTokenTransfers resp, err := client.R(). SetBody(transfer). SetQueryParam("confirm", strconv.FormatBool(confirm)). @@ -419,9 +419,10 @@ func TransferTokens(t *testing.T, client *resty.Client, poolName string, transfe return &transferOut } -func GetTokenTransfers(t *testing.T, client *resty.Client, poolName string) (transfers []*fftypes.TokenTransfer) { - path := fmt.Sprintf(urlTokenTransfers, poolName) +func GetTokenTransfers(t *testing.T, client *resty.Client, poolProtocolID string) (transfers []*fftypes.TokenTransfer) { + path := urlTokenTransfers resp, err := client.R(). + SetQueryParam("poolprotocolid", poolProtocolID). SetResult(&transfers). Get(path) require.NoError(t, err) @@ -429,10 +430,11 @@ func GetTokenTransfers(t *testing.T, client *resty.Client, poolName string) (tra return transfers } -func GetTokenAccount(t *testing.T, client *resty.Client, poolName, tokenIndex, key string) (account *fftypes.TokenAccount) { - var accounts []*fftypes.TokenAccount - path := fmt.Sprintf(urlTokenAccounts, poolName) +func GetTokenBalance(t *testing.T, client *resty.Client, poolProtocolID, tokenIndex, key string) (account *fftypes.TokenBalance) { + var accounts []*fftypes.TokenBalance + path := urlTokenBalances resp, err := client.R(). + SetQueryParam("poolprotocolid", poolProtocolID). SetQueryParam("tokenIndex", tokenIndex). SetQueryParam("key", key). SetResult(&accounts). diff --git a/test/e2e/tokens_test.go b/test/e2e/tokens_test.go index c345c72c3d..5da4d308ed 100644 --- a/test/e2e/tokens_test.go +++ b/test/e2e/tokens_test.go @@ -59,6 +59,8 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { assert.Equal(suite.T(), fftypes.TokenTypeFungible, pools[0].Type) assert.NotEmpty(suite.T(), pools[0].ProtocolID) + poolProtocolID := pools[0].ProtocolID + <-received2 pools = GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) assert.Equal(suite.T(), 1, len(pools)) @@ -68,34 +70,38 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { assert.Equal(suite.T(), fftypes.TokenTypeFungible, pools[0].Type) assert.NotEmpty(suite.T(), pools[0].ProtocolID) - transfer := &fftypes.TokenTransferInput{} - transfer.Amount.Int().SetInt64(1) - MintTokens(suite.T(), suite.testState.client1, poolName, transfer, false) + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{Amount: *fftypes.NewBigInt(1)}, + Pool: poolName, + } + MintTokens(suite.T(), suite.testState.client1, transfer, false) <-received1 - transfers := GetTokenTransfers(suite.T(), suite.testState.client1, poolName) + transfers := GetTokenTransfers(suite.T(), suite.testState.client1, poolProtocolID) assert.Equal(suite.T(), 1, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) assert.Equal(suite.T(), fftypes.TokenTransferTypeMint, transfers[0].Type) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client1, poolName, "", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client1, poolProtocolID, "", map[string]int64{ suite.testState.org1.Identity: 1, }) <-received2 - transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolName) + transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolProtocolID) assert.Equal(suite.T(), 1, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) assert.Equal(suite.T(), fftypes.TokenTransferTypeMint, transfers[0].Type) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolName, "", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client2, poolProtocolID, "", map[string]int64{ suite.testState.org1.Identity: 1, }) transfer = &fftypes.TokenTransferInput{ TokenTransfer: fftypes.TokenTransfer{ - To: suite.testState.org2.Identity, + To: suite.testState.org2.Identity, + Amount: *fftypes.NewBigInt(1), }, + Pool: poolName, Message: &fftypes.MessageInOut{ InlineData: fftypes.InlineData{ { @@ -104,12 +110,11 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { }, }, } - transfer.Amount.Int().SetInt64(1) - TransferTokens(suite.T(), suite.testState.client1, poolName, transfer, false) + TransferTokens(suite.T(), suite.testState.client1, transfer, false) <-received1 // one event for transfer <-received1 // one event for message - transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolName) + transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolProtocolID) assert.Equal(suite.T(), 2, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) assert.Equal(suite.T(), fftypes.TokenTransferTypeTransfer, transfers[0].Type) @@ -117,47 +122,49 @@ func (suite *TokensTestSuite) TestE2EFungibleTokensAsync() { data := GetDataForMessage(suite.T(), suite.testState.client1, suite.testState.startTime, transfers[0].MessageHash) assert.Equal(suite.T(), 1, len(data)) assert.Equal(suite.T(), `"payment for data"`, data[0].Value.String()) - validateAccountBalances(suite.T(), suite.testState.client1, poolName, "", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client1, poolProtocolID, "", map[string]int64{ suite.testState.org1.Identity: 0, suite.testState.org2.Identity: 1, }) <-received2 // one event for transfer <-received2 // one event for message - transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolName) + transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolProtocolID) assert.Equal(suite.T(), 2, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) assert.Equal(suite.T(), fftypes.TokenTransferTypeTransfer, transfers[0].Type) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolName, "", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client2, poolProtocolID, "", map[string]int64{ suite.testState.org1.Identity: 0, suite.testState.org2.Identity: 1, }) - transfer = &fftypes.TokenTransferInput{} - transfer.Amount.Int().SetInt64(1) - BurnTokens(suite.T(), suite.testState.client2, poolName, transfer, false) + transfer = &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{Amount: *fftypes.NewBigInt(1)}, + Pool: poolName, + } + BurnTokens(suite.T(), suite.testState.client2, transfer, false) <-received2 - transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolName) + transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolProtocolID) assert.Equal(suite.T(), 3, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) assert.Equal(suite.T(), fftypes.TokenTransferTypeBurn, transfers[0].Type) assert.Equal(suite.T(), "", transfers[0].TokenIndex) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolName, "", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client2, poolProtocolID, "", map[string]int64{ suite.testState.org1.Identity: 0, suite.testState.org2.Identity: 0, }) <-received1 - transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolName) + transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolProtocolID) assert.Equal(suite.T(), 3, len(transfers)) assert.Equal(suite.T(), "erc1155", transfers[0].Connector) assert.Equal(suite.T(), fftypes.TokenTransferTypeBurn, transfers[0].Type) assert.Equal(suite.T(), "", transfers[0].TokenIndex) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client1, poolName, "", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client1, poolProtocolID, "", map[string]int64{ suite.testState.org1.Identity: 0, suite.testState.org2.Identity: 0, }) @@ -183,6 +190,8 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { assert.Equal(suite.T(), fftypes.TokenTypeNonFungible, poolOut.Type) assert.NotEmpty(suite.T(), poolOut.ProtocolID) + poolProtocolID := poolOut.ProtocolID + <-received1 <-received2 pools = GetTokenPools(suite.T(), suite.testState.client1, suite.testState.startTime) @@ -192,24 +201,26 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { assert.Equal(suite.T(), fftypes.TokenTypeNonFungible, pools[0].Type) assert.NotEmpty(suite.T(), pools[0].ProtocolID) - transfer := &fftypes.TokenTransferInput{} - transfer.Amount.Int().SetInt64(1) - transferOut := MintTokens(suite.T(), suite.testState.client1, poolName, transfer, true) + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{Amount: *fftypes.NewBigInt(1)}, + Pool: poolName, + } + transferOut := MintTokens(suite.T(), suite.testState.client1, transfer, true) assert.Equal(suite.T(), fftypes.TokenTransferTypeMint, transferOut.Type) assert.Equal(suite.T(), "1", transferOut.TokenIndex) assert.Equal(suite.T(), int64(1), transferOut.Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client1, poolName, "1", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client1, poolProtocolID, "1", map[string]int64{ suite.testState.org1.Identity: 1, }) <-received1 <-received2 - transfers := GetTokenTransfers(suite.T(), suite.testState.client2, poolName) + transfers := GetTokenTransfers(suite.T(), suite.testState.client2, poolProtocolID) assert.Equal(suite.T(), 1, len(transfers)) assert.Equal(suite.T(), fftypes.TokenTransferTypeMint, transfers[0].Type) assert.Equal(suite.T(), "1", transfers[0].TokenIndex) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolName, "1", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client2, poolProtocolID, "1", map[string]int64{ suite.testState.org1.Identity: 1, }) @@ -217,7 +228,9 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { TokenTransfer: fftypes.TokenTransfer{ TokenIndex: "1", To: suite.testState.org2.Identity, + Amount: *fftypes.NewBigInt(1), }, + Pool: poolName, Message: &fftypes.MessageInOut{ InlineData: fftypes.InlineData{ { @@ -226,15 +239,14 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { }, }, } - transfer.Amount.Int().SetInt64(1) - transferOut = TransferTokens(suite.T(), suite.testState.client1, poolName, transfer, true) + transferOut = TransferTokens(suite.T(), suite.testState.client1, transfer, true) assert.Equal(suite.T(), fftypes.TokenTransferTypeTransfer, transferOut.Type) assert.Equal(suite.T(), "1", transferOut.TokenIndex) assert.Equal(suite.T(), int64(1), transferOut.Amount.Int().Int64()) data := GetDataForMessage(suite.T(), suite.testState.client1, suite.testState.startTime, transferOut.MessageHash) assert.Equal(suite.T(), 1, len(data)) assert.Equal(suite.T(), `"ownership change"`, data[0].Value.String()) - validateAccountBalances(suite.T(), suite.testState.client1, poolName, "1", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client1, poolProtocolID, "1", map[string]int64{ suite.testState.org1.Identity: 0, suite.testState.org2.Identity: 1, }) @@ -243,12 +255,12 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { <-received1 // one event for message <-received2 // one event for transfer <-received2 // one event for message - transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolName) + transfers = GetTokenTransfers(suite.T(), suite.testState.client2, poolProtocolID) assert.Equal(suite.T(), 2, len(transfers)) assert.Equal(suite.T(), fftypes.TokenTransferTypeTransfer, transfers[0].Type) assert.Equal(suite.T(), "1", transfers[0].TokenIndex) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolName, "1", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client2, poolProtocolID, "1", map[string]int64{ suite.testState.org1.Identity: 0, suite.testState.org2.Identity: 1, }) @@ -256,26 +268,27 @@ func (suite *TokensTestSuite) TestE2ENonFungibleTokensSync() { transfer = &fftypes.TokenTransferInput{ TokenTransfer: fftypes.TokenTransfer{ TokenIndex: "1", + Amount: *fftypes.NewBigInt(1), }, + Pool: poolName, } - transfer.Amount.Int().SetInt64(1) - transferOut = BurnTokens(suite.T(), suite.testState.client2, poolName, transfer, true) + transferOut = BurnTokens(suite.T(), suite.testState.client2, transfer, true) assert.Equal(suite.T(), fftypes.TokenTransferTypeBurn, transferOut.Type) assert.Equal(suite.T(), "1", transferOut.TokenIndex) assert.Equal(suite.T(), int64(1), transferOut.Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client2, poolName, "1", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client2, poolProtocolID, "1", map[string]int64{ suite.testState.org1.Identity: 0, suite.testState.org2.Identity: 0, }) <-received2 <-received1 - transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolName) + transfers = GetTokenTransfers(suite.T(), suite.testState.client1, poolProtocolID) assert.Equal(suite.T(), 3, len(transfers)) assert.Equal(suite.T(), fftypes.TokenTransferTypeBurn, transfers[0].Type) assert.Equal(suite.T(), "1", transfers[0].TokenIndex) assert.Equal(suite.T(), int64(1), transfers[0].Amount.Int().Int64()) - validateAccountBalances(suite.T(), suite.testState.client1, poolName, "1", map[string]int64{ + validateAccountBalances(suite.T(), suite.testState.client1, poolProtocolID, "1", map[string]int64{ suite.testState.org1.Identity: 0, suite.testState.org2.Identity: 0, })