From 9fdab20afd0f1f7f42f8732a6cdab7828c2f0da5 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Wed, 27 Oct 2021 18:15:07 -0400 Subject: [PATCH 1/7] post-tokens POST burn, mint, transfer by type Signed-off-by: David Echelberger --- docs/swagger/swagger.yaml | 6 +-- ...rn.go => route_post_token_burn_by_type.go} | 6 +-- ... => route_post_token_burn_by_type_test.go} | 4 +- ...nt.go => route_post_token_mint_by_type.go} | 6 +-- ... => route_post_token_mint_by_type_test.go} | 4 +- ...o => route_post_token_transfer_by_type.go} | 6 +-- ...route_post_token_transfer_by_type_test.go} | 4 +- internal/apiserver/routes.go | 6 +-- internal/assets/manager.go | 6 +-- internal/assets/token_transfer.go | 6 +-- internal/assets/token_transfer_test.go | 40 +++++++++---------- mocks/assetmocks/manager.go | 12 +++--- 12 files changed, 53 insertions(+), 53 deletions(-) rename internal/apiserver/{route_post_token_burn.go => route_post_token_burn_by_type.go} (89%) rename internal/apiserver/{route_post_token_burn_test.go => route_post_token_burn_by_type_test.go} (88%) rename internal/apiserver/{route_post_token_mint.go => route_post_token_mint_by_type.go} (89%) rename internal/apiserver/{route_post_token_mint_test.go => route_post_token_mint_by_type_test.go} (88%) rename internal/apiserver/{route_post_token_transfer.go => route_post_token_transfer_by_type.go} (89%) rename internal/apiserver/{route_post_token_transfer_test.go => route_post_token_transfer_by_type_test.go} (88%) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index d0b1745b63..2d1dc81b24 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -4598,7 +4598,7 @@ paths: /namespaces/{ns}/tokens/{type}/pools/{name}/burn: post: description: 'TODO: Description' - operationId: postTokenBurn + operationId: postTokenBurnByType parameters: - description: 'TODO: Description' in: path @@ -4815,7 +4815,7 @@ paths: /namespaces/{ns}/tokens/{type}/pools/{name}/mint: post: description: 'TODO: Description' - operationId: postTokenMint + operationId: postTokenMintByType parameters: - description: 'TODO: Description' in: path @@ -5196,7 +5196,7 @@ paths: description: "" post: description: 'TODO: Description' - operationId: postTokenTransfer + operationId: postTokenTransferByType parameters: - description: 'TODO: Description' in: path diff --git a/internal/apiserver/route_post_token_burn.go b/internal/apiserver/route_post_token_burn_by_type.go similarity index 89% rename from internal/apiserver/route_post_token_burn.go rename to internal/apiserver/route_post_token_burn_by_type.go index 232e080f52..36de79899a 100644 --- a/internal/apiserver/route_post_token_burn.go +++ b/internal/apiserver/route_post_token_burn_by_type.go @@ -26,8 +26,8 @@ import ( "github.com/hyperledger/firefly/pkg/fftypes" ) -var postTokenBurn = &oapispec.Route{ - Name: "postTokenBurn", +var postTokenBurnByType = &oapispec.Route{ + Name: "postTokenBurnByType", Path: "namespaces/{ns}/tokens/{type}/pools/{name}/burn", Method: http.MethodPost, PathParams: []*oapispec.PathParam{ @@ -47,6 +47,6 @@ var postTokenBurn = &oapispec.Route{ JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - return r.Or.Assets().BurnTokens(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) + return r.Or.Assets().BurnTokensByType(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) }, } diff --git a/internal/apiserver/route_post_token_burn_test.go b/internal/apiserver/route_post_token_burn_by_type_test.go similarity index 88% rename from internal/apiserver/route_post_token_burn_test.go rename to internal/apiserver/route_post_token_burn_by_type_test.go index 6b4de5ee98..82d258da70 100644 --- a/internal/apiserver/route_post_token_burn_test.go +++ b/internal/apiserver/route_post_token_burn_by_type_test.go @@ -28,7 +28,7 @@ import ( "github.com/stretchr/testify/mock" ) -func TestPostTokenBurn(t *testing.T) { +func TestPostTokenBurnByType(t *testing.T) { o, r := newTestAPIServer() mam := &assetmocks.Manager{} o.On("Assets").Return(mam) @@ -39,7 +39,7 @@ func TestPostTokenBurn(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mam.On("BurnTokens", mock.Anything, "ns1", "tok1", "pool1", mock.AnythingOfType("*fftypes.TokenTransferInput"), false). + mam.On("BurnTokensByType", mock.Anything, "ns1", "tok1", "pool1", mock.AnythingOfType("*fftypes.TokenTransferInput"), false). Return(&fftypes.TokenTransfer{}, nil) r.ServeHTTP(res, req) diff --git a/internal/apiserver/route_post_token_mint.go b/internal/apiserver/route_post_token_mint_by_type.go similarity index 89% rename from internal/apiserver/route_post_token_mint.go rename to internal/apiserver/route_post_token_mint_by_type.go index 608f12d7a9..440366d3cc 100644 --- a/internal/apiserver/route_post_token_mint.go +++ b/internal/apiserver/route_post_token_mint_by_type.go @@ -26,8 +26,8 @@ import ( "github.com/hyperledger/firefly/pkg/fftypes" ) -var postTokenMint = &oapispec.Route{ - Name: "postTokenMint", +var postTokenMintByType = &oapispec.Route{ + Name: "postTokenMintByType", Path: "namespaces/{ns}/tokens/{type}/pools/{name}/mint", Method: http.MethodPost, PathParams: []*oapispec.PathParam{ @@ -47,6 +47,6 @@ var postTokenMint = &oapispec.Route{ JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - return r.Or.Assets().MintTokens(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) + return r.Or.Assets().MintTokensByType(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) }, } diff --git a/internal/apiserver/route_post_token_mint_test.go b/internal/apiserver/route_post_token_mint_by_type_test.go similarity index 88% rename from internal/apiserver/route_post_token_mint_test.go rename to internal/apiserver/route_post_token_mint_by_type_test.go index 1b4eca0c64..32f615276d 100644 --- a/internal/apiserver/route_post_token_mint_test.go +++ b/internal/apiserver/route_post_token_mint_by_type_test.go @@ -28,7 +28,7 @@ import ( "github.com/stretchr/testify/mock" ) -func TestPostTokenMint(t *testing.T) { +func TestPostTokenMintByType(t *testing.T) { o, r := newTestAPIServer() mam := &assetmocks.Manager{} o.On("Assets").Return(mam) @@ -39,7 +39,7 @@ func TestPostTokenMint(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mam.On("MintTokens", mock.Anything, "ns1", "tok1", "pool1", mock.AnythingOfType("*fftypes.TokenTransferInput"), false). + mam.On("MintTokensByType", mock.Anything, "ns1", "tok1", "pool1", mock.AnythingOfType("*fftypes.TokenTransferInput"), false). Return(&fftypes.TokenTransfer{}, nil) r.ServeHTTP(res, req) diff --git a/internal/apiserver/route_post_token_transfer.go b/internal/apiserver/route_post_token_transfer_by_type.go similarity index 89% rename from internal/apiserver/route_post_token_transfer.go rename to internal/apiserver/route_post_token_transfer_by_type.go index 0d70356818..c838f0b91f 100644 --- a/internal/apiserver/route_post_token_transfer.go +++ b/internal/apiserver/route_post_token_transfer_by_type.go @@ -26,8 +26,8 @@ import ( "github.com/hyperledger/firefly/pkg/fftypes" ) -var postTokenTransfer = &oapispec.Route{ - Name: "postTokenTransfer", +var postTokenTransferByType = &oapispec.Route{ + Name: "postTokenTransferByType", Path: "namespaces/{ns}/tokens/{type}/pools/{name}/transfers", Method: http.MethodPost, PathParams: []*oapispec.PathParam{ @@ -47,6 +47,6 @@ var postTokenTransfer = &oapispec.Route{ JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - return r.Or.Assets().TransferTokens(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) + return r.Or.Assets().TransferTokensByType(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) }, } diff --git a/internal/apiserver/route_post_token_transfer_test.go b/internal/apiserver/route_post_token_transfer_by_type_test.go similarity index 88% rename from internal/apiserver/route_post_token_transfer_test.go rename to internal/apiserver/route_post_token_transfer_by_type_test.go index a9b2e195c0..0658466ef2 100644 --- a/internal/apiserver/route_post_token_transfer_test.go +++ b/internal/apiserver/route_post_token_transfer_by_type_test.go @@ -28,7 +28,7 @@ import ( "github.com/stretchr/testify/mock" ) -func TestPostTokenTransfer(t *testing.T) { +func TestPostTokenTransferByType(t *testing.T) { o, r := newTestAPIServer() mam := &assetmocks.Manager{} o.On("Assets").Return(mam) @@ -39,7 +39,7 @@ func TestPostTokenTransfer(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mam.On("TransferTokens", mock.Anything, "ns1", "tok1", "pool1", mock.AnythingOfType("*fftypes.TokenTransferInput"), false). + mam.On("TransferTokensByType", mock.Anything, "ns1", "tok1", "pool1", mock.AnythingOfType("*fftypes.TokenTransferInput"), false). Return(&fftypes.TokenTransfer{}, nil) r.ServeHTTP(res, req) diff --git a/internal/apiserver/routes.go b/internal/apiserver/routes.go index f1d981a0ab..6bdb2f1f6f 100644 --- a/internal/apiserver/routes.go +++ b/internal/apiserver/routes.go @@ -87,8 +87,8 @@ var routes = []*oapispec.Route{ getTokenTransfers, getTokenTransfersByPool, getTokenTransferByID, - postTokenMint, - postTokenBurn, - postTokenTransfer, + postTokenMintByType, + postTokenBurnByType, + postTokenTransferByType, getTokenConnectors, } diff --git a/internal/assets/manager.go b/internal/assets/manager.go index 8750aa3ccb..790aeb6b70 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -47,9 +47,9 @@ type Manager interface { 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, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) - BurnTokens(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) - TransferTokens(ctx context.Context, ns, connector, poolName 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) + 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) GetTokenConnectors(ctx context.Context, ns string) ([]*fftypes.TokenConnector, error) // Bound token callbacks diff --git a/internal/assets/token_transfer.go b/internal/assets/token_transfer.go index 015ac8cae5..6d2cc16f90 100644 --- a/internal/assets/token_transfer.go +++ b/internal/assets/token_transfer.go @@ -93,7 +93,7 @@ func (s *transferSender) setDefaults() { s.transfer.Connector = s.connector } -func (am *assetManager) MintTokens(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { +func (am *assetManager) MintTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { transfer.Type = fftypes.TokenTransferTypeMint if transfer.Key == "" { org, err := am.identity.GetLocalOrganization(ctx) @@ -116,7 +116,7 @@ func (am *assetManager) MintTokens(ctx context.Context, ns, connector, poolName return &transfer.TokenTransfer, err } -func (am *assetManager) BurnTokens(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { +func (am *assetManager) BurnTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { transfer.Type = fftypes.TokenTransferTypeBurn if transfer.Key == "" { org, err := am.identity.GetLocalOrganization(ctx) @@ -139,7 +139,7 @@ func (am *assetManager) BurnTokens(ctx context.Context, ns, connector, poolName return &transfer.TokenTransfer, err } -func (am *assetManager) TransferTokens(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { +func (am *assetManager) TransferTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { transfer.Type = fftypes.TokenTransferTypeTransfer if transfer.Key == "" { org, err := am.identity.GetLocalOrganization(ctx) diff --git a/internal/assets/token_transfer_test.go b/internal/assets/token_transfer_test.go index 8f014fd0dc..ec43abdf9e 100644 --- a/internal/assets/token_transfer_test.go +++ b/internal/assets/token_transfer_test.go @@ -124,7 +124,7 @@ func TestMintTokensSuccess(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - _, err := am.MintTokens(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) assert.NoError(t, err) } @@ -135,7 +135,7 @@ func TestMintTokensBadPlugin(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) - _, err := am.MintTokens(context.Background(), "", "", "", &fftypes.TokenTransferInput{}, false) + _, err := am.MintTokensByType(context.Background(), "", "", "", &fftypes.TokenTransferInput{}, false) assert.Regexp(t, "FF10272", err) } @@ -151,7 +151,7 @@ func TestMintTokensBadPool(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(nil, fmt.Errorf("pop")) - _, err := am.MintTokens(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) assert.EqualError(t, err, "pop") } @@ -167,7 +167,7 @@ func TestMintTokensIdentityFail(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - _, err := am.MintTokens(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) assert.EqualError(t, err, "pop") } @@ -189,7 +189,7 @@ func TestMintTokensFail(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - _, err := am.MintTokens(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) assert.EqualError(t, err, "pop") } @@ -209,7 +209,7 @@ func TestMintTokensOperationFail(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(fmt.Errorf("pop")) - _, err := am.MintTokens(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) assert.EqualError(t, err, "pop") } @@ -239,7 +239,7 @@ func TestMintTokensConfirm(t *testing.T) { }). Return(&fftypes.TokenTransfer{}, nil) - _, err := am.MintTokens(context.Background(), "ns1", "magic-tokens", "pool1", mint, true) + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, true) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -266,7 +266,7 @@ func TestBurnTokensSuccess(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - _, err := am.BurnTokens(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) + _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) assert.NoError(t, err) mim.AssertExpectations(t) @@ -286,7 +286,7 @@ func TestBurnTokensIdentityFail(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - _, err := am.BurnTokens(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) + _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) assert.EqualError(t, err, "pop") } @@ -316,7 +316,7 @@ func TestBurnTokensConfirm(t *testing.T) { }). Return(&fftypes.TokenTransfer{}, nil) - _, err := am.BurnTokens(context.Background(), "ns1", "magic-tokens", "pool1", burn, true) + _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, true) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -348,7 +348,7 @@ func TestTransferTokensSuccess(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.NoError(t, err) mim.AssertExpectations(t) @@ -373,7 +373,7 @@ func TestTransferTokensIdentityFail(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.EqualError(t, err, "pop") } @@ -386,7 +386,7 @@ func TestTransferTokensNoFromOrTo(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) - _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.Regexp(t, "FF10280", err) mim.AssertExpectations(t) @@ -443,7 +443,7 @@ func TestTransferTokensTransactionFail(t *testing.T) { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(fmt.Errorf("pop")) - _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.EqualError(t, err, "pop") mim.AssertExpectations(t) @@ -488,7 +488,7 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) mms.On("Send", context.Background()).Return(nil) - _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.NoError(t, err) assert.Equal(t, *hash, *transfer.MessageHash) @@ -525,7 +525,7 @@ func TestTransferTokensWithBroadcastMessageFail(t *testing.T) { mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) mms.On("Send", context.Background()).Return(fmt.Errorf("pop")) - _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.EqualError(t, err, "pop") mbm.AssertExpectations(t) @@ -574,7 +574,7 @@ func TestTransferTokensWithPrivateMessage(t *testing.T) { mpm.On("NewMessage", "ns1", transfer.Message).Return(mms) mms.On("Send", context.Background()).Return(nil) - _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.NoError(t, err) assert.Equal(t, *hash, *transfer.MessageHash) @@ -612,7 +612,7 @@ func TestTransferTokensWithInvalidMessage(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) - _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.Regexp(t, "FF10287", err) mim.AssertExpectations(t) @@ -649,7 +649,7 @@ func TestTransferTokensConfirm(t *testing.T) { }). Return(&fftypes.TokenTransfer{}, nil) - _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, true) + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, true) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -781,7 +781,7 @@ func TestTransferTokensWithBroadcastMessageConfirm(t *testing.T) { Return(mms) mms.On("SendAndWait", context.Background()).Return(nil) - _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, true) + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, true) assert.NoError(t, err) assert.Nil(t, transfer.Message) assert.Equal(t, *hash, *transfer.MessageHash) diff --git a/mocks/assetmocks/manager.go b/mocks/assetmocks/manager.go index 059502600a..b62b09e9b7 100644 --- a/mocks/assetmocks/manager.go +++ b/mocks/assetmocks/manager.go @@ -20,8 +20,8 @@ type Manager struct { mock.Mock } -// BurnTokens provides a mock function with given fields: ctx, ns, connector, poolName, transfer, waitConfirm -func (_m *Manager) BurnTokens(ctx context.Context, ns string, connector string, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { +// BurnTokensByType provides a mock function with given fields: ctx, ns, connector, poolName, transfer, waitConfirm +func (_m *Manager) BurnTokensByType(ctx context.Context, ns string, connector string, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { ret := _m.Called(ctx, ns, connector, poolName, transfer, waitConfirm) var r0 *fftypes.TokenTransfer @@ -350,8 +350,8 @@ func (_m *Manager) GetTokenTransfersByPool(ctx context.Context, ns string, conne return r0, r1, r2 } -// MintTokens provides a mock function with given fields: ctx, ns, connector, poolName, transfer, waitConfirm -func (_m *Manager) MintTokens(ctx context.Context, ns string, connector string, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { +// MintTokensByType provides a mock function with given fields: ctx, ns, connector, poolName, transfer, waitConfirm +func (_m *Manager) MintTokensByType(ctx context.Context, ns string, connector string, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { ret := _m.Called(ctx, ns, connector, poolName, transfer, waitConfirm) var r0 *fftypes.TokenTransfer @@ -417,8 +417,8 @@ func (_m *Manager) TokenPoolCreated(tk tokens.Plugin, pool *fftypes.TokenPool, p return r0 } -// TransferTokens provides a mock function with given fields: ctx, ns, connector, poolName, transfer, waitConfirm -func (_m *Manager) TransferTokens(ctx context.Context, ns string, connector string, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { +// TransferTokensByType provides a mock function with given fields: ctx, ns, connector, poolName, transfer, waitConfirm +func (_m *Manager) TransferTokensByType(ctx context.Context, ns string, connector string, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { ret := _m.Called(ctx, ns, connector, poolName, transfer, waitConfirm) var r0 *fftypes.TokenTransfer From 222c22f534aaff07b29250d05f39f0444b4dd981 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Thu, 28 Oct 2021 01:55:17 -0400 Subject: [PATCH 2/7] post-tokens new POST mint, burn, transfer routes Signed-off-by: David Echelberger --- docs/swagger/swagger.yaml | 724 +++++++++++++++++- internal/apiserver/route_post_token_burn.go | 50 ++ .../apiserver/route_post_token_burn_test.go | 47 ++ internal/apiserver/route_post_token_mint.go | 50 ++ .../apiserver/route_post_token_mint_test.go | 47 ++ .../apiserver/route_post_token_transfer.go | 50 ++ .../route_post_token_transfer_test.go | 47 ++ internal/apiserver/routes.go | 3 + internal/assets/manager.go | 3 + internal/assets/token_transfer.go | 141 ++++ internal/assets/token_transfer_test.go | 708 +++++++++++++++-- internal/i18n/en_translations.go | 1 + mocks/assetmocks/manager.go | 69 ++ pkg/fftypes/tokentransfer.go | 1 + 14 files changed, 1852 insertions(+), 89 deletions(-) create mode 100644 internal/apiserver/route_post_token_burn.go create mode 100644 internal/apiserver/route_post_token_burn_test.go create mode 100644 internal/apiserver/route_post_token_mint.go create mode 100644 internal/apiserver/route_post_token_mint_test.go create mode 100644 internal/apiserver/route_post_token_transfer.go create mode 100644 internal/apiserver/route_post_token_transfer_test.go diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 2d1dc81b24..badb3ef0b0 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -4718,6 +4718,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: @@ -4757,6 +4759,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: @@ -4792,6 +4796,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: @@ -4935,6 +4941,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: @@ -4974,6 +4982,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: @@ -5009,6 +5019,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: @@ -5173,6 +5185,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: @@ -5316,6 +5330,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: @@ -5355,6 +5371,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: @@ -5390,6 +5408,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: @@ -5522,6 +5542,217 @@ paths: description: Success default: description: "" + /namespaces/{ns}/tokens/burn: + post: + description: 'TODO: Description' + operationId: postTokenBurn + parameters: + - description: 'TODO: Description' + in: path + name: ns + required: true + schema: + example: default + type: string + - description: When true the HTTP request blocks until the message is confirmed + in: query + name: confirm + schema: + 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 + requestBody: + content: + application/json: + schema: + properties: + amount: {} + connector: + type: string + created: {} + from: + type: string + key: + type: string + localId: {} + message: + properties: + batch: {} + confirmed: {} + data: + items: + properties: + hash: {} + id: {} + type: object + type: array + group: + properties: + ledger: {} + members: + items: + properties: + identity: + type: string + node: + type: string + type: object + type: array + name: + type: string + type: object + hash: {} + header: + properties: + author: + type: string + cid: {} + created: {} + datahash: {} + group: {} + id: {} + key: + type: string + namespace: + type: string + tag: + type: string + topics: + items: + type: string + type: array + txtype: + type: string + type: + enum: + - definition + - broadcast + - private + - groupinit + - transfer_broadcast + - transfer_private + type: string + type: object + pins: + items: + type: string + type: array + state: + enum: + - ready + - pending + - confirmed + - rejected + type: string + type: object + messageHash: {} + namespace: + type: string + pool: + type: string + poolProtocolId: + type: string + protocolId: + type: string + to: + type: string + tokenIndex: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + enum: + - mint + - burn + - transfer + type: string + type: object + responses: + "200": + content: + application/json: + schema: + properties: + amount: {} + connector: + type: string + created: {} + from: + type: string + key: + type: string + localId: {} + messageHash: {} + namespace: + type: string + pool: + type: string + poolProtocolId: + type: string + protocolId: + type: string + to: + type: string + tokenIndex: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + type: string + type: object + description: Success + "202": + content: + application/json: + schema: + properties: + amount: {} + connector: + type: string + created: {} + from: + type: string + key: + type: string + localId: {} + messageHash: {} + namespace: + type: string + pool: + type: string + poolProtocolId: + type: string + protocolId: + type: string + to: + type: string + tokenIndex: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + type: string + type: object + description: Success + default: + description: "" /namespaces/{ns}/tokens/connectors: get: description: 'TODO: Description' @@ -5555,10 +5786,10 @@ paths: description: Success default: description: "" - /namespaces/{ns}/tokens/pools: - get: + /namespaces/{ns}/tokens/mint: + post: description: 'TODO: Description' - operationId: getTokenPools + operationId: postTokenMint parameters: - description: 'TODO: Description' in: path @@ -5567,6 +5798,11 @@ paths: schema: example: default type: string + - description: When true the HTTP request blocks until the message is confirmed + in: query + name: confirm + schema: + type: string - description: Server-side request timeout (millseconds, or set a custom suffix like 10s) in: header @@ -5574,37 +5810,243 @@ paths: schema: default: 120s type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: connector - schema: - type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: created - schema: - type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: id - 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: message - schema: - type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' - in: query - name: name - schema: - type: string - - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + requestBody: + content: + application/json: + schema: + properties: + amount: {} + connector: + type: string + created: {} + from: + type: string + key: + type: string + localId: {} + message: + properties: + batch: {} + confirmed: {} + data: + items: + properties: + hash: {} + id: {} + type: object + type: array + group: + properties: + ledger: {} + members: + items: + properties: + identity: + type: string + node: + type: string + type: object + type: array + name: + type: string + type: object + hash: {} + header: + properties: + author: + type: string + cid: {} + created: {} + datahash: {} + group: {} + id: {} + key: + type: string + namespace: + type: string + tag: + type: string + topics: + items: + type: string + type: array + txtype: + type: string + type: + enum: + - definition + - broadcast + - private + - groupinit + - transfer_broadcast + - transfer_private + type: string + type: object + pins: + items: + type: string + type: array + state: + enum: + - ready + - pending + - confirmed + - rejected + type: string + type: object + messageHash: {} + namespace: + type: string + pool: + type: string + poolProtocolId: + type: string + protocolId: + type: string + to: + type: string + tokenIndex: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + enum: + - mint + - burn + - transfer + type: string + type: object + responses: + "200": + content: + application/json: + schema: + properties: + amount: {} + connector: + type: string + created: {} + from: + type: string + key: + type: string + localId: {} + messageHash: {} + namespace: + type: string + pool: + type: string + poolProtocolId: + type: string + protocolId: + type: string + to: + type: string + tokenIndex: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + type: string + type: object + description: Success + "202": + content: + application/json: + schema: + properties: + amount: {} + connector: + type: string + created: {} + from: + type: string + key: + type: string + localId: {} + messageHash: {} + namespace: + type: string + pool: + type: string + poolProtocolId: + type: string + protocolId: + type: string + to: + type: string + tokenIndex: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + type: string + type: object + description: Success + default: + description: "" + /namespaces/{ns}/tokens/pools: + get: + description: 'TODO: Description' + operationId: getTokenPools + 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: connector + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: created + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: id + 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: message + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: name + schema: + type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' in: query name: namespace schema: @@ -5898,6 +6340,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: @@ -5919,6 +6363,216 @@ paths: description: Success default: description: "" + post: + description: 'TODO: Description' + operationId: postTokenTransfer + parameters: + - description: 'TODO: Description' + in: path + name: ns + required: true + schema: + example: default + type: string + - description: When true the HTTP request blocks until the message is confirmed + in: query + name: confirm + schema: + 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 + requestBody: + content: + application/json: + schema: + properties: + amount: {} + connector: + type: string + created: {} + from: + type: string + key: + type: string + localId: {} + message: + properties: + batch: {} + confirmed: {} + data: + items: + properties: + hash: {} + id: {} + type: object + type: array + group: + properties: + ledger: {} + members: + items: + properties: + identity: + type: string + node: + type: string + type: object + type: array + name: + type: string + type: object + hash: {} + header: + properties: + author: + type: string + cid: {} + created: {} + datahash: {} + group: {} + id: {} + key: + type: string + namespace: + type: string + tag: + type: string + topics: + items: + type: string + type: array + txtype: + type: string + type: + enum: + - definition + - broadcast + - private + - groupinit + - transfer_broadcast + - transfer_private + type: string + type: object + pins: + items: + type: string + type: array + state: + enum: + - ready + - pending + - confirmed + - rejected + type: string + type: object + messageHash: {} + namespace: + type: string + pool: + type: string + poolProtocolId: + type: string + protocolId: + type: string + to: + type: string + tokenIndex: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + enum: + - mint + - burn + - transfer + type: string + type: object + responses: + "200": + content: + application/json: + schema: + properties: + amount: {} + connector: + type: string + created: {} + from: + type: string + key: + type: string + localId: {} + messageHash: {} + namespace: + type: string + pool: + type: string + poolProtocolId: + type: string + protocolId: + type: string + to: + type: string + tokenIndex: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + type: string + type: object + description: Success + "202": + content: + application/json: + schema: + properties: + amount: {} + connector: + type: string + created: {} + from: + type: string + key: + type: string + localId: {} + messageHash: {} + namespace: + type: string + pool: + type: string + poolProtocolId: + type: string + protocolId: + type: string + to: + type: string + tokenIndex: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + type: string + type: object + description: Success + default: + description: "" /namespaces/{ns}/tokens/transfers/{transferID}: get: description: 'TODO: Description' @@ -5962,6 +6616,8 @@ paths: messageHash: {} namespace: type: string + pool: + type: string poolProtocolId: type: string protocolId: diff --git a/internal/apiserver/route_post_token_burn.go b/internal/apiserver/route_post_token_burn.go new file mode 100644 index 0000000000..074a0d743e --- /dev/null +++ b/internal/apiserver/route_post_token_burn.go @@ -0,0 +1,50 @@ +// 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" + "strings" + + "github.com/hyperledger/firefly/internal/config" + "github.com/hyperledger/firefly/internal/i18n" + "github.com/hyperledger/firefly/internal/oapispec" + "github.com/hyperledger/firefly/pkg/fftypes" +) + +var postTokenBurn = &oapispec.Route{ + Name: "postTokenBurn", + Path: "namespaces/{ns}/tokens/burn", + Method: http.MethodPost, + PathParams: []*oapispec.PathParam{ + {Name: "ns", ExampleFromConf: config.NamespacesDefault, Description: i18n.MsgTBD}, + }, + QueryParams: []*oapispec.QueryParam{ + {Name: "confirm", Description: i18n.MsgConfirmQueryParam, IsBool: true}, + }, + FilterFactory: nil, + Description: i18n.MsgTBD, + JSONInputValue: func() interface{} { return &fftypes.TokenTransferInput{} }, + JSONInputMask: []string{"Type", "LocalID", "PoolProtocolID", "To", "ProtocolID", "MessageHash", "TX", "Created"}, + JSONOutputValue: func() interface{} { return &fftypes.TokenTransfer{} }, + JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK}, + JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { + waitConfirm := strings.EqualFold(r.QP["confirm"], "true") + r.SuccessStatus = syncRetcode(waitConfirm) + return r.Or.Assets().BurnTokens(r.Ctx, r.PP["ns"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) + }, +} diff --git a/internal/apiserver/route_post_token_burn_test.go b/internal/apiserver/route_post_token_burn_test.go new file mode 100644 index 0000000000..549520f19c --- /dev/null +++ b/internal/apiserver/route_post_token_burn_test.go @@ -0,0 +1,47 @@ +// 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 ( + "bytes" + "encoding/json" + "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 TestPostTokenBurn(t *testing.T) { + o, r := newTestAPIServer() + mam := &assetmocks.Manager{} + o.On("Assets").Return(mam) + input := fftypes.TokenTransferInput{} + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(&input) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/tokens/burn", &buf) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + mam.On("BurnTokens", mock.Anything, "ns1", mock.AnythingOfType("*fftypes.TokenTransferInput"), false). + Return(&fftypes.TokenTransfer{}, nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 202, res.Result().StatusCode) +} diff --git a/internal/apiserver/route_post_token_mint.go b/internal/apiserver/route_post_token_mint.go new file mode 100644 index 0000000000..4d8802c5d7 --- /dev/null +++ b/internal/apiserver/route_post_token_mint.go @@ -0,0 +1,50 @@ +// 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" + "strings" + + "github.com/hyperledger/firefly/internal/config" + "github.com/hyperledger/firefly/internal/i18n" + "github.com/hyperledger/firefly/internal/oapispec" + "github.com/hyperledger/firefly/pkg/fftypes" +) + +var postTokenMint = &oapispec.Route{ + Name: "postTokenMint", + Path: "namespaces/{ns}/tokens/mint", + Method: http.MethodPost, + PathParams: []*oapispec.PathParam{ + {Name: "ns", ExampleFromConf: config.NamespacesDefault, Description: i18n.MsgTBD}, + }, + QueryParams: []*oapispec.QueryParam{ + {Name: "confirm", Description: i18n.MsgConfirmQueryParam, IsBool: true}, + }, + FilterFactory: nil, + Description: i18n.MsgTBD, + JSONInputValue: func() interface{} { return &fftypes.TokenTransferInput{} }, + JSONInputMask: []string{"Type", "LocalID", "PoolProtocolID", "TokenIndex", "From", "ProtocolID", "MessageHash", "TX", "Created"}, + JSONOutputValue: func() interface{} { return &fftypes.TokenTransfer{} }, + JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK}, + JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { + waitConfirm := strings.EqualFold(r.QP["confirm"], "true") + r.SuccessStatus = syncRetcode(waitConfirm) + return r.Or.Assets().MintTokens(r.Ctx, r.PP["ns"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) + }, +} diff --git a/internal/apiserver/route_post_token_mint_test.go b/internal/apiserver/route_post_token_mint_test.go new file mode 100644 index 0000000000..2ab2412363 --- /dev/null +++ b/internal/apiserver/route_post_token_mint_test.go @@ -0,0 +1,47 @@ +// 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 ( + "bytes" + "encoding/json" + "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 TestPostTokenMint(t *testing.T) { + o, r := newTestAPIServer() + mam := &assetmocks.Manager{} + o.On("Assets").Return(mam) + input := fftypes.TokenTransferInput{} + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(&input) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/tokens/mint", &buf) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + mam.On("MintTokens", mock.Anything, "ns1", mock.AnythingOfType("*fftypes.TokenTransferInput"), false). + Return(&fftypes.TokenTransfer{}, nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 202, res.Result().StatusCode) +} diff --git a/internal/apiserver/route_post_token_transfer.go b/internal/apiserver/route_post_token_transfer.go new file mode 100644 index 0000000000..1fe71e50ac --- /dev/null +++ b/internal/apiserver/route_post_token_transfer.go @@ -0,0 +1,50 @@ +// 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" + "strings" + + "github.com/hyperledger/firefly/internal/config" + "github.com/hyperledger/firefly/internal/i18n" + "github.com/hyperledger/firefly/internal/oapispec" + "github.com/hyperledger/firefly/pkg/fftypes" +) + +var postTokenTransfer = &oapispec.Route{ + Name: "postTokenTransfer", + Path: "namespaces/{ns}/tokens/transfers", + Method: http.MethodPost, + PathParams: []*oapispec.PathParam{ + {Name: "ns", ExampleFromConf: config.NamespacesDefault, Description: i18n.MsgTBD}, + }, + QueryParams: []*oapispec.QueryParam{ + {Name: "confirm", Description: i18n.MsgConfirmQueryParam, IsBool: true}, + }, + FilterFactory: nil, + Description: i18n.MsgTBD, + JSONInputValue: func() interface{} { return &fftypes.TokenTransferInput{} }, + JSONInputMask: []string{"Type", "LocalID", "PoolProtocolID", "ProtocolID", "MessageHash", "Namespace", "TX", "Created"}, + JSONOutputValue: func() interface{} { return &fftypes.TokenTransfer{} }, + JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK}, + JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { + waitConfirm := strings.EqualFold(r.QP["confirm"], "true") + r.SuccessStatus = syncRetcode(waitConfirm) + return r.Or.Assets().TransferTokens(r.Ctx, r.PP["ns"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) + }, +} diff --git a/internal/apiserver/route_post_token_transfer_test.go b/internal/apiserver/route_post_token_transfer_test.go new file mode 100644 index 0000000000..3ac1548c58 --- /dev/null +++ b/internal/apiserver/route_post_token_transfer_test.go @@ -0,0 +1,47 @@ +// 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 ( + "bytes" + "encoding/json" + "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 TestPostTokenTransfer(t *testing.T) { + o, r := newTestAPIServer() + mam := &assetmocks.Manager{} + o.On("Assets").Return(mam) + input := fftypes.TokenTransferInput{} + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(&input) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/tokens/transfers", &buf) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + mam.On("TransferTokens", mock.Anything, "ns1", mock.AnythingOfType("*fftypes.TokenTransferInput"), false). + Return(&fftypes.TokenTransfer{}, nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 202, res.Result().StatusCode) +} diff --git a/internal/apiserver/routes.go b/internal/apiserver/routes.go index 6bdb2f1f6f..93ac467dee 100644 --- a/internal/apiserver/routes.go +++ b/internal/apiserver/routes.go @@ -87,8 +87,11 @@ var routes = []*oapispec.Route{ getTokenTransfers, getTokenTransfersByPool, getTokenTransferByID, + postTokenMint, postTokenMintByType, + postTokenBurn, postTokenBurnByType, + postTokenTransfer, postTokenTransferByType, getTokenConnectors, } diff --git a/internal/assets/manager.go b/internal/assets/manager.go index 790aeb6b70..0fadc06590 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -47,8 +47,11 @@ type Manager interface { 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) diff --git a/internal/assets/token_transfer.go b/internal/assets/token_transfer.go index 6d2cc16f90..afd41208a3 100644 --- a/internal/assets/token_transfer.go +++ b/internal/assets/token_transfer.go @@ -93,6 +93,44 @@ func (s *transferSender) setDefaults() { s.transfer.Connector = s.connector } +func (am *assetManager) MintTokens(ctx context.Context, ns string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { + connector := transfer.Connector + if connector == "" { + connector, err = am.getTokenConnectorName(ctx, ns) + if err != nil { + return nil, err + } + } + poolName := transfer.Pool + if poolName == "" { + poolName, err = am.getTokenPoolName(ctx, ns) + if err != nil { + return nil, err + } + } + + transfer.Type = fftypes.TokenTransferTypeMint + if transfer.Key == "" { + org, err := am.identity.GetLocalOrganization(ctx) + if err != nil { + return nil, err + } + transfer.Key = org.Identity + } + transfer.From = "" + if transfer.To == "" { + transfer.To = transfer.Key + } + + sender := am.NewTransfer(ns, connector, poolName, transfer) + if waitConfirm { + err = sender.SendAndWait(ctx) + } else { + err = sender.Send(ctx) + } + return &transfer.TokenTransfer, err +} + func (am *assetManager) MintTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { transfer.Type = fftypes.TokenTransferTypeMint if transfer.Key == "" { @@ -116,6 +154,44 @@ func (am *assetManager) MintTokensByType(ctx context.Context, ns, connector, poo return &transfer.TokenTransfer, err } +func (am *assetManager) BurnTokens(ctx context.Context, ns string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { + connector := transfer.Connector + if connector == "" { + connector, err = am.getTokenConnectorName(ctx, ns) + if err != nil { + return nil, err + } + } + poolName := transfer.Pool + if poolName == "" { + poolName, err = am.getTokenPoolName(ctx, ns) + if err != nil { + return nil, err + } + } + + transfer.Type = fftypes.TokenTransferTypeBurn + if transfer.Key == "" { + org, err := am.identity.GetLocalOrganization(ctx) + if err != nil { + return nil, err + } + transfer.Key = org.Identity + } + if transfer.From == "" { + transfer.From = transfer.Key + } + transfer.To = "" + + sender := am.NewTransfer(ns, connector, poolName, transfer) + if waitConfirm { + err = sender.SendAndWait(ctx) + } else { + err = sender.Send(ctx) + } + return &transfer.TokenTransfer, err +} + func (am *assetManager) BurnTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { transfer.Type = fftypes.TokenTransferTypeBurn if transfer.Key == "" { @@ -139,6 +215,49 @@ func (am *assetManager) BurnTokensByType(ctx context.Context, ns, connector, poo return &transfer.TokenTransfer, err } +func (am *assetManager) TransferTokens(ctx context.Context, ns string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { + connector := transfer.Connector + if connector == "" { + connector, err = am.getTokenConnectorName(ctx, ns) + if err != nil { + return nil, err + } + } + poolName := transfer.Pool + if poolName == "" { + poolName, err = am.getTokenPoolName(ctx, ns) + if err != nil { + return nil, err + } + } + + transfer.Type = fftypes.TokenTransferTypeTransfer + if transfer.Key == "" { + org, err := am.identity.GetLocalOrganization(ctx) + if err != nil { + return nil, err + } + transfer.Key = org.Identity + } + if transfer.From == "" { + transfer.From = transfer.Key + } + if transfer.To == "" { + transfer.To = transfer.Key + } + if transfer.From == transfer.To { + return nil, i18n.NewError(ctx, i18n.MsgCannotTransferToSelf) + } + + sender := am.NewTransfer(ns, connector, poolName, transfer) + if waitConfirm { + err = sender.SendAndWait(ctx) + } else { + err = sender.Send(ctx) + } + return &transfer.TokenTransfer, err +} + func (am *assetManager) TransferTokensByType(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { transfer.Type = fftypes.TokenTransferTypeTransfer if transfer.Key == "" { @@ -286,3 +405,25 @@ func (s *transferSender) buildTransferMessage(ctx context.Context, ns string, in return nil, i18n.NewError(ctx, i18n.MsgInvalidMessageType, allowedTypes) } } + +func (am *assetManager) getTokenConnectorName(ctx context.Context, ns string) (string, error) { + tokenConnectors, err := am.GetTokenConnectors(ctx, ns) + if err != nil { + return "", err + } + if len(tokenConnectors) != 1 { + return "", i18n.NewError(ctx, i18n.MsgFieldNotSpecified, "connector") + } + return tokenConnectors[0].Name, nil +} + +func (am *assetManager) getTokenPoolName(ctx context.Context, ns string) (string, error) { + tokenPools, _, err := am.GetTokenPools(ctx, ns, database.TokenPoolQueryFactory.NewFilter(context.Background()).And()) + if err != nil { + return "", err + } + if len(tokenPools) != 1 { + return "", i18n.NewError(ctx, i18n.MsgFieldNotSpecified, "pool") + } + return tokenPools[0].Name, nil +} diff --git a/internal/assets/token_transfer_test.go b/internal/assets/token_transfer_test.go index ec43abdf9e..f6fb6f55d3 100644 --- a/internal/assets/token_transfer_test.go +++ b/internal/assets/token_transfer_test.go @@ -110,6 +110,143 @@ func TestMintTokensSuccess(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("MintTokens", context.Background(), mock.Anything, &mint.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.MintTokens(context.Background(), "ns1", mint, false) + assert.NoError(t, err) +} + +func TestMintTokensUnspecifiedAndSingleConnector(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.MintTokens(context.Background(), "", &fftypes.TokenTransferInput{}, false) + assert.Regexp(t, "FF10272", err) +} + +func TestMintTokensIdentityFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + + _, err := am.MintTokens(context.Background(), "ns1", mint, false) + assert.EqualError(t, err, "pop") +} + +func TestMintTokensFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("MintTokens", context.Background(), mock.Anything, &mint.TokenTransfer).Return(fmt.Errorf("pop")) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.MintTokens(context.Background(), "ns1", mint, false) + assert.EqualError(t, err, "pop") +} + +func TestMintTokensOperationFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(fmt.Errorf("pop")) + + _, err := am.MintTokens(context.Background(), "ns1", mint, false) + assert.EqualError(t, err, "pop") +} + +func TestMintTokensConfirm(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + msa := am.syncasync.(*syncasyncmocks.Bridge) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("MintTokens", context.Background(), mock.Anything, &mint.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + msa.On("SendConfirmTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send(context.Background()) + }). + Return(&fftypes.TokenTransfer{}, nil) + + _, err := am.MintTokens(context.Background(), "ns1", mint, true) + assert.NoError(t, err) + + mdi.AssertExpectations(t) + mdm.AssertExpectations(t) + msa.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestMintTokensByTypeSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) @@ -128,7 +265,7 @@ func TestMintTokensSuccess(t *testing.T) { assert.NoError(t, err) } -func TestMintTokensBadPlugin(t *testing.T) { +func TestMintTokensByTypeBadPlugin(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -139,7 +276,7 @@ func TestMintTokensBadPlugin(t *testing.T) { assert.Regexp(t, "FF10272", err) } -func TestMintTokensBadPool(t *testing.T) { +func TestMintTokensByTypeBadPool(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -155,7 +292,7 @@ func TestMintTokensBadPool(t *testing.T) { assert.EqualError(t, err, "pop") } -func TestMintTokensIdentityFail(t *testing.T) { +func TestMintTokensByTypeIdentityFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -171,7 +308,7 @@ func TestMintTokensIdentityFail(t *testing.T) { assert.EqualError(t, err, "pop") } -func TestMintTokensFail(t *testing.T) { +func TestMintTokensByTypeFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -193,7 +330,7 @@ func TestMintTokensFail(t *testing.T) { assert.EqualError(t, err, "pop") } -func TestMintTokensOperationFail(t *testing.T) { +func TestMintTokensByTypeOperationFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -213,7 +350,7 @@ func TestMintTokensOperationFail(t *testing.T) { assert.EqualError(t, err, "pop") } -func TestMintTokensConfirm(t *testing.T) { +func TestMintTokensByTypeConfirm(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -254,6 +391,8 @@ func TestBurnTokensSuccess(t *testing.T) { burn := &fftypes.TokenTransferInput{} burn.Amount.Int().SetInt64(5) + burn.Connector = "magic-tokens" + burn.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) @@ -266,7 +405,7 @@ func TestBurnTokensSuccess(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) + _, err := am.BurnTokens(context.Background(), "ns1", burn, false) assert.NoError(t, err) mim.AssertExpectations(t) @@ -280,13 +419,15 @@ func TestBurnTokensIdentityFail(t *testing.T) { burn := &fftypes.TokenTransferInput{} burn.Amount.Int().SetInt64(5) + burn.Connector = "magic-tokens" + burn.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) + _, err := am.BurnTokens(context.Background(), "ns1", burn, false) assert.EqualError(t, err, "pop") } @@ -294,6 +435,85 @@ func TestBurnTokensConfirm(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "magic-tokens" + burn.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + msa := am.syncasync.(*syncasyncmocks.Bridge) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + msa.On("SendConfirmTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send(context.Background()) + }). + Return(&fftypes.TokenTransfer{}, nil) + + _, err := am.BurnTokens(context.Background(), "ns1", burn, true) + assert.NoError(t, err) + + mdi.AssertExpectations(t) + mdm.AssertExpectations(t) + msa.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestBurnTokensByTypeSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestBurnTokensByTypeIdentityFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + + _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) + assert.EqualError(t, err, "pop") +} + +func TestBurnTokensByTypeConfirm(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + burn := &fftypes.TokenTransferInput{} burn.Amount.Int().SetInt64(5) @@ -336,6 +556,8 @@ func TestTransferTokensSuccess(t *testing.T) { }, } transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) @@ -348,7 +570,7 @@ func TestTransferTokensSuccess(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) assert.NoError(t, err) mim.AssertExpectations(t) @@ -367,13 +589,15 @@ func TestTransferTokensIdentityFail(t *testing.T) { }, } transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) assert.EqualError(t, err, "pop") } @@ -382,47 +606,18 @@ func TestTransferTokensNoFromOrTo(t *testing.T) { defer cancel() transfer := &fftypes.TokenTransferInput{} + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) - _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) assert.Regexp(t, "FF10280", err) mim.AssertExpectations(t) } -func TestTransferTokensInvalidType(t *testing.T) { - am, cancel := newTestAssets(t) - defer cancel() - - transfer := &fftypes.TokenTransferInput{ - TokenTransfer: fftypes.TokenTransfer{ - From: "A", - To: "B", - }, - } - transfer.Amount.Int().SetInt64(5) - - mdi := am.database.(*databasemocks.Plugin) - mdi.On("GetTokenPool", am.ctx, "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - mdi.On("UpsertTransaction", am.ctx, mock.MatchedBy(func(tx *fftypes.Transaction) bool { - return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer - }), false).Return(nil) - mdi.On("UpsertOperation", am.ctx, mock.Anything, false).Return(nil) - - sender := &transferSender{ - mgr: am, - namespace: "ns1", - connector: "magic-tokens", - poolName: "pool1", - transfer: transfer, - } - assert.PanicsWithValue(t, "unknown transfer type: ", func() { - sender.Send(am.ctx) - }) -} - func TestTransferTokensTransactionFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -434,6 +629,8 @@ func TestTransferTokensTransactionFail(t *testing.T) { }, } transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) @@ -443,7 +640,7 @@ func TestTransferTokensTransactionFail(t *testing.T) { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(fmt.Errorf("pop")) - _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) assert.EqualError(t, err, "pop") mim.AssertExpectations(t) @@ -472,6 +669,8 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { }, } transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) @@ -488,7 +687,7 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) mms.On("Send", context.Background()).Return(nil) - _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) assert.NoError(t, err) assert.Equal(t, *hash, *transfer.MessageHash) @@ -517,6 +716,8 @@ func TestTransferTokensWithBroadcastMessageFail(t *testing.T) { }, } transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" mim := am.identity.(*identitymanagermocks.Manager) mbm := am.broadcast.(*broadcastmocks.Manager) @@ -525,7 +726,7 @@ func TestTransferTokensWithBroadcastMessageFail(t *testing.T) { mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) mms.On("Send", context.Background()).Return(fmt.Errorf("pop")) - _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) assert.EqualError(t, err, "pop") mbm.AssertExpectations(t) @@ -558,6 +759,8 @@ func TestTransferTokensWithPrivateMessage(t *testing.T) { }, } transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) @@ -574,7 +777,7 @@ func TestTransferTokensWithPrivateMessage(t *testing.T) { mpm.On("NewMessage", "ns1", transfer.Message).Return(mms) mms.On("Send", context.Background()).Return(nil) - _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) assert.NoError(t, err) assert.Equal(t, *hash, *transfer.MessageHash) @@ -608,11 +811,13 @@ func TestTransferTokensWithInvalidMessage(t *testing.T) { }, } transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) - _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) assert.Regexp(t, "FF10287", err) mim.AssertExpectations(t) @@ -629,6 +834,8 @@ func TestTransferTokensConfirm(t *testing.T) { }, } transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mdm := am.data.(*datamocks.Manager) @@ -649,7 +856,7 @@ func TestTransferTokensConfirm(t *testing.T) { }). Return(&fftypes.TokenTransfer{}, nil) - _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, true) + _, err := am.TransferTokens(context.Background(), "ns1", transfer, true) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -658,31 +865,422 @@ func TestTransferTokensConfirm(t *testing.T) { mti.AssertExpectations(t) } -func TestTransferTokensBeforeSendCallback(t *testing.T) { +func TestTransferTokensWithBroadcastMessageConfirm(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() + hash := fftypes.NewRandB32() transfer := &fftypes.TokenTransferInput{ TokenTransfer: fftypes.TokenTransfer{ - Type: fftypes.TokenTransferTypeTransfer, From: "A", To: "B", }, + Message: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Hash: hash, + }, + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, } transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) + mbm := am.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - - sender := am.NewTransfer("ns1", "magic-tokens", "pool1", transfer) - + mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) + mms.On("BeforeSend", mock.Anything). + Run(func(args mock.Arguments) { + cb := args[0].(sysmessaging.BeforeSendCallback) + cb(context.Background()) + }). + Return(mms) + mms.On("SendAndWait", context.Background()).Return(nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, true) + assert.NoError(t, err) + assert.Nil(t, transfer.Message) + assert.Equal(t, *hash, *transfer.MessageHash) + + mbm.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestTransferTokensByTypeSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestTransferTokensByTypeIdentityFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + assert.EqualError(t, err, "pop") +} + +func TestTransferTokensByTypeNoFromOrTo(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{} + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + assert.Regexp(t, "FF10280", err) + + mim.AssertExpectations(t) +} + +func TestTransferTokensByTypeInvalidType(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mdi.On("GetTokenPool", am.ctx, "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mdi.On("UpsertTransaction", am.ctx, mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", am.ctx, mock.Anything, false).Return(nil) + + sender := &transferSender{ + mgr: am, + namespace: "ns1", + connector: "magic-tokens", + poolName: "pool1", + transfer: transfer, + } + assert.PanicsWithValue(t, "unknown transfer type: ", func() { + sender.Send(am.ctx) + }) +} + +func TestTransferTokensByTypeTransactionFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(fmt.Errorf("pop")) + + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + assert.EqualError(t, err, "pop") + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) +} + +func TestTransferTokensByTypeWithBroadcastMessage(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + hash := fftypes.NewRandB32() + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Hash: hash, + }, + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mbm := am.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) + mms.On("Send", context.Background()).Return(nil) + + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + assert.NoError(t, err) + assert.Equal(t, *hash, *transfer.MessageHash) + + mbm.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestTransferTokensByTypeWithBroadcastMessageFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + + mim := am.identity.(*identitymanagermocks.Manager) + mbm := am.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) + mms.On("Send", context.Background()).Return(fmt.Errorf("pop")) + + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + assert.EqualError(t, err, "pop") + + mbm.AssertExpectations(t) + mim.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestTransferTokensByTypeWithPrivateMessage(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + hash := fftypes.NewRandB32() + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Type: fftypes.MessageTypeTransferPrivate, + }, + Hash: hash, + }, + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mpm := am.messaging.(*privatemessagingmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mpm.On("NewMessage", "ns1", transfer.Message).Return(mms) + mms.On("Send", context.Background()).Return(nil) + + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + assert.NoError(t, err) + assert.Equal(t, *hash, *transfer.MessageHash) + + mpm.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestTransferTokensByTypeWithInvalidMessage(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Type: fftypes.MessageTypeDefinition, + }, + }, + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + assert.Regexp(t, "FF10287", err) + + mim.AssertExpectations(t) +} + +func TestTransferTokensByTypeConfirm(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + msa := am.syncasync.(*syncasyncmocks.Bridge) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + msa.On("SendConfirmTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send(context.Background()) + }). + Return(&fftypes.TokenTransfer{}, nil) + + _, err := am.TransferTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", transfer, true) + assert.NoError(t, err) + + mdi.AssertExpectations(t) + mdm.AssertExpectations(t) + msa.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestTransferTokensByTypeBeforeSendCallback(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + Type: fftypes.TokenTransferTypeTransfer, + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + sender := am.NewTransfer("ns1", "magic-tokens", "pool1", transfer) + called := false sender.BeforeSend(func(ctx context.Context) error { called = true @@ -698,7 +1296,7 @@ func TestTransferTokensBeforeSendCallback(t *testing.T) { mti.AssertExpectations(t) } -func TestTransferTokensBeforeSendCallbackFail(t *testing.T) { +func TestTransferTokensByTypeBeforeSendCallbackFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -737,7 +1335,7 @@ func TestTransferTokensBeforeSendCallbackFail(t *testing.T) { mti.AssertExpectations(t) } -func TestTransferTokensWithBroadcastMessageConfirm(t *testing.T) { +func TestTransferTokensByTypeWithBroadcastMessageConfirm(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() diff --git a/internal/i18n/en_translations.go b/internal/i18n/en_translations.go index 681c226848..f5e92bf31e 100644 --- a/internal/i18n/en_translations.go +++ b/internal/i18n/en_translations.go @@ -206,4 +206,5 @@ var ( MsgFailedToDecodeCertificate = ffm("FF10286", "Failed to decode certificate: %s", 500) MsgInvalidMessageType = ffm("FF10287", "Invalid message type - allowed types are %s", 400) MsgNoUUID = ffm("FF10288", "Field '%s' must not be a UUID", 400) + MsgFieldNotSpecified = ffm("FF10289", "Field '%s' must be specified", 400) ) diff --git a/mocks/assetmocks/manager.go b/mocks/assetmocks/manager.go index b62b09e9b7..ec7ccc9d7e 100644 --- a/mocks/assetmocks/manager.go +++ b/mocks/assetmocks/manager.go @@ -20,6 +20,29 @@ type Manager struct { mock.Mock } +// BurnTokens provides a mock function with given fields: ctx, ns, transfer, waitConfirm +func (_m *Manager) BurnTokens(ctx context.Context, ns string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { + ret := _m.Called(ctx, ns, transfer, waitConfirm) + + var r0 *fftypes.TokenTransfer + if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.TokenTransferInput, bool) *fftypes.TokenTransfer); ok { + r0 = rf(ctx, ns, transfer, waitConfirm) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*fftypes.TokenTransfer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, *fftypes.TokenTransferInput, bool) error); ok { + r1 = rf(ctx, ns, transfer, waitConfirm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // BurnTokensByType provides a mock function with given fields: ctx, ns, connector, poolName, transfer, waitConfirm func (_m *Manager) BurnTokensByType(ctx context.Context, ns string, connector string, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { ret := _m.Called(ctx, ns, connector, poolName, transfer, waitConfirm) @@ -350,6 +373,29 @@ func (_m *Manager) GetTokenTransfersByPool(ctx context.Context, ns string, conne return r0, r1, r2 } +// MintTokens provides a mock function with given fields: ctx, ns, transfer, waitConfirm +func (_m *Manager) MintTokens(ctx context.Context, ns string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { + ret := _m.Called(ctx, ns, transfer, waitConfirm) + + var r0 *fftypes.TokenTransfer + if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.TokenTransferInput, bool) *fftypes.TokenTransfer); ok { + r0 = rf(ctx, ns, transfer, waitConfirm) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*fftypes.TokenTransfer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, *fftypes.TokenTransferInput, bool) error); ok { + r1 = rf(ctx, ns, transfer, waitConfirm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // MintTokensByType provides a mock function with given fields: ctx, ns, connector, poolName, transfer, waitConfirm func (_m *Manager) MintTokensByType(ctx context.Context, ns string, connector string, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { ret := _m.Called(ctx, ns, connector, poolName, transfer, waitConfirm) @@ -417,6 +463,29 @@ func (_m *Manager) TokenPoolCreated(tk tokens.Plugin, pool *fftypes.TokenPool, p return r0 } +// TransferTokens provides a mock function with given fields: ctx, ns, transfer, waitConfirm +func (_m *Manager) TransferTokens(ctx context.Context, ns string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { + ret := _m.Called(ctx, ns, transfer, waitConfirm) + + var r0 *fftypes.TokenTransfer + if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.TokenTransferInput, bool) *fftypes.TokenTransfer); ok { + r0 = rf(ctx, ns, transfer, waitConfirm) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*fftypes.TokenTransfer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, *fftypes.TokenTransferInput, bool) error); ok { + r1 = rf(ctx, ns, transfer, waitConfirm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // TransferTokensByType provides a mock function with given fields: ctx, ns, connector, poolName, transfer, waitConfirm func (_m *Manager) TransferTokensByType(ctx context.Context, ns string, connector string, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { ret := _m.Called(ctx, ns, connector, poolName, transfer, waitConfirm) diff --git a/pkg/fftypes/tokentransfer.go b/pkg/fftypes/tokentransfer.go index d23829b12f..54ec36c753 100644 --- a/pkg/fftypes/tokentransfer.go +++ b/pkg/fftypes/tokentransfer.go @@ -35,6 +35,7 @@ type TokenTransfer struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` Amount BigInt `json:"amount"` + Pool string `json:"pool,omitempty"` ProtocolID string `json:"protocolId,omitempty"` MessageHash *Bytes32 `json:"messageHash,omitempty"` Created *FFTime `json:"created,omitempty"` From a925bbe99d91bd60c8654b3d53175ef172d421d2 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Thu, 28 Oct 2021 16:37:10 -0400 Subject: [PATCH 3/7] post-tokens testing new POST routes Signed-off-by: David Echelberger --- internal/assets/token_transfer.go | 6 +- internal/assets/token_transfer_test.go | 1381 +++++++++++++++++++++++- 2 files changed, 1337 insertions(+), 50 deletions(-) diff --git a/internal/assets/token_transfer.go b/internal/assets/token_transfer.go index b8d75e821d..911aa2d389 100644 --- a/internal/assets/token_transfer.go +++ b/internal/assets/token_transfer.go @@ -437,11 +437,13 @@ func (am *assetManager) getTokenConnectorName(ctx context.Context, ns string) (s } func (am *assetManager) getTokenPoolName(ctx context.Context, ns string) (string, error) { - tokenPools, _, err := am.GetTokenPools(ctx, ns, database.TokenPoolQueryFactory.NewFilter(context.Background()).And()) + f := database.TokenPoolQueryFactory.NewFilter(ctx).And() + f.Limit(1).Count(true) + tokenPools, fr, err := am.GetTokenPools(ctx, ns, f) if err != nil { return "", err } - if len(tokenPools) != 1 { + if *fr.TotalCount != 1 { return "", i18n.NewError(ctx, i18n.MsgFieldNotSpecified, "pool") } return tokenPools[0].Name, nil diff --git a/internal/assets/token_transfer_test.go b/internal/assets/token_transfer_test.go index 47dfa602d1..c69ae4cb3f 100644 --- a/internal/assets/token_transfer_test.go +++ b/internal/assets/token_transfer_test.go @@ -30,6 +30,7 @@ import ( "github.com/hyperledger/firefly/mocks/tokenmocks" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" + "github.com/hyperledger/firefly/pkg/tokens" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -129,12 +130,14 @@ func TestMintTokensSuccess(t *testing.T) { assert.NoError(t, err) } -func TestMintTokensByTypeSuccess(t *testing.T) { +func TestMintTokenUnknownConnectorSuccess(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) + mint.Connector = "" + mint.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) @@ -147,59 +150,248 @@ func TestMintTokensByTypeSuccess(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + _, err := am.MintTokens(context.Background(), "ns1", mint, false) assert.NoError(t, err) } -func TestMintTokensByTypeBadPlugin(t *testing.T) { +func TestMintTokenUnknownConnectorNoConnectors(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "" + mint.Pool = "pool1" + + am.tokens = make(map[string]tokens.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) - _, err := am.MintTokensByType(context.Background(), "", "", "", &fftypes.TokenTransferInput{}, false) - assert.Regexp(t, "FF10272", err) + _, err := am.MintTokens(context.Background(), "ns1", mint, false) + assert.Regexp(t, "FF10291", err) } -func TestMintTokensByTypeBadPool(t *testing.T) { +func TestMintTokenUnknownConnectorMultipleConnectors(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "" + mint.Pool = "pool1" + + am.tokens["magic-tokens"] = nil + am.tokens["magic-tokens2"] = nil + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.MintTokens(context.Background(), "ns1", mint, false) + assert.Regexp(t, "FF10291", err) +} + +func TestMintTokenUnknownConnectorBadNamespace(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "" + mint.Pool = "pool1" + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.MintTokens(context.Background(), "", mint, false) + assert.Regexp(t, "FF10131", err) +} + +func TestMintTokenUnknownPoolSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + fb := database.TokenPoolQueryFactory.NewFilter(context.Background()) + f := fb.And() + f.Limit(1).Count(true) + tokenPools := []*fftypes.TokenPool{ + { + Name: "pool1", + }, + } + totalCount := int64(1) + filterResult := &database.FilterResult{ + TotalCount: &totalCount, + } + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPools", context.Background(), mock.MatchedBy((func(f database.AndFilter) bool { + info, _ := f.Finalize() + return info.Count && info.Limit == 1 + }))).Return(tokenPools, filterResult, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("MintTokens", context.Background(), mock.Anything, &mint.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.MintTokens(context.Background(), "ns1", mint, false) + assert.NoError(t, err) +} + +func TestMintTokenUnknownPoolNoPools(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + fb := database.TokenPoolQueryFactory.NewFilter(context.Background()) + f := fb.And() + f.Limit(1).Count(true) + tokenPools := []*fftypes.TokenPool{} + totalCount := int64(0) + filterResult := &database.FilterResult{ + TotalCount: &totalCount, + } + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPools", context.Background(), mock.MatchedBy((func(f database.AndFilter) bool { + info, _ := f.Finalize() + return info.Count && info.Limit == 1 + }))).Return(tokenPools, filterResult, nil) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.MintTokens(context.Background(), "ns1", mint, false) + assert.Regexp(t, "FF10291", err) +} + +func TestMintTokenUnknownPoolMultiplePools(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + fb := database.TokenPoolQueryFactory.NewFilter(context.Background()) + f := fb.And() + f.Limit(1).Count(true) + tokenPools := []*fftypes.TokenPool{ + { + Name: "pool1", + }, + { + Name: "pool2", + }, + } + totalCount := int64(2) + filterResult := &database.FilterResult{ + TotalCount: &totalCount, + } + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPools", context.Background(), mock.MatchedBy((func(f database.AndFilter) bool { + info, _ := f.Finalize() + return info.Count && info.Limit == 1 + }))).Return(tokenPools, filterResult, nil) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.MintTokens(context.Background(), "ns1", mint, false) + assert.Regexp(t, "FF10291", err) +} + +func TestMintTokenUnknownPoolBadNamespace(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "" + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.MintTokens(context.Background(), "", mint, false) + assert.Regexp(t, "FF10131", err) +} + +func TestMintTokensNoPool(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(nil, fmt.Errorf("pop")) - _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + _, err := am.MintTokens(context.Background(), "ns1", mint, false) assert.EqualError(t, err, "pop") } -func TestMintTokensByTypeIdentityFail(t *testing.T) { +func TestMintTokensBadPool(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(nil, fmt.Errorf("pop")) + + _, err := am.MintTokens(context.Background(), "ns1", mint, false) + assert.EqualError(t, err, "pop") +} + +func TestMintTokensIdentityFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + _, err := am.MintTokens(context.Background(), "ns1", mint, false) assert.EqualError(t, err, "pop") } -func TestMintTokensByTypeFail(t *testing.T) { +func TestMintTokensFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) @@ -212,16 +404,18 @@ func TestMintTokensByTypeFail(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + _, err := am.MintTokens(context.Background(), "ns1", mint, false) assert.EqualError(t, err, "pop") } -func TestMintTokensByTypeOperationFail(t *testing.T) { +func TestMintTokensOperationFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) @@ -232,16 +426,18 @@ func TestMintTokensByTypeOperationFail(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(fmt.Errorf("pop")) - _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + _, err := am.MintTokens(context.Background(), "ns1", mint, false) assert.EqualError(t, err, "pop") } -func TestMintTokensByTypeConfirm(t *testing.T) { +func TestMintTokensConfirm(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) + mint.Connector = "magic-tokens" + mint.Pool = "pool1" mdi := am.database.(*databasemocks.Plugin) mdm := am.data.(*datamocks.Manager) @@ -262,7 +458,7 @@ func TestMintTokensByTypeConfirm(t *testing.T) { }). Return(&fftypes.TokenTransfer{}, nil) - _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, true) + _, err := am.MintTokens(context.Background(), "ns1", mint, true) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -271,82 +467,119 @@ func TestMintTokensByTypeConfirm(t *testing.T) { mti.AssertExpectations(t) } -func TestBurnTokensSuccess(t *testing.T) { +func TestMintTokensByTypeSuccess(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - burn := &fftypes.TokenTransferInput{} - burn.Amount.Int().SetInt64(5) - burn.Connector = "magic-tokens" - burn.Pool = "pool1" + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mti.On("MintTokens", context.Background(), mock.Anything, &mint.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - _, err := am.BurnTokens(context.Background(), "ns1", burn, false) + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) assert.NoError(t, err) +} - mim.AssertExpectations(t) - mdi.AssertExpectations(t) - mti.AssertExpectations(t) +func TestMintTokensByTypeBadPlugin(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.MintTokensByType(context.Background(), "", "", "", &fftypes.TokenTransferInput{}, false) + assert.Regexp(t, "FF10272", err) } -func TestBurnTokensByTypeSuccess(t *testing.T) { +func TestMintTokensByTypeBadPool(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - burn := &fftypes.TokenTransferInput{} - burn.Amount.Int().SetInt64(5) + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(nil, fmt.Errorf("pop")) + + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + assert.EqualError(t, err, "pop") +} + +func TestMintTokensByTypeIdentityFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + assert.EqualError(t, err, "pop") +} + +func TestMintTokensByTypeFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mti.On("MintTokens", context.Background(), mock.Anything, &mint.TokenTransfer).Return(fmt.Errorf("pop")) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) - assert.NoError(t, err) - - mim.AssertExpectations(t) - mdi.AssertExpectations(t) - mti.AssertExpectations(t) + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) + assert.EqualError(t, err, "pop") } -func TestBurnTokensByTypeIdentityFail(t *testing.T) { +func TestMintTokensByTypeOperationFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - burn := &fftypes.TokenTransferInput{} - burn.Amount.Int().SetInt64(5) + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) - mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(fmt.Errorf("pop")) - _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, false) assert.EqualError(t, err, "pop") } -func TestBurnTokensByTypeConfirm(t *testing.T) { +func TestMintTokensByTypeConfirm(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - burn := &fftypes.TokenTransferInput{} - burn.Amount.Int().SetInt64(5) + mint := &fftypes.TokenTransferInput{} + mint.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) mdm := am.data.(*datamocks.Manager) @@ -355,7 +588,7 @@ func TestBurnTokensByTypeConfirm(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mti.On("MintTokens", context.Background(), mock.Anything, &mint.TokenTransfer).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(nil) @@ -367,7 +600,7 @@ func TestBurnTokensByTypeConfirm(t *testing.T) { }). Return(&fftypes.TokenTransfer{}, nil) - _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, true) + _, err := am.MintTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", mint, true) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -376,15 +609,864 @@ func TestBurnTokensByTypeConfirm(t *testing.T) { mti.AssertExpectations(t) } -func TestTransferTokensSuccess(t *testing.T) { +func TestBurnTokensSuccess(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - transfer := &fftypes.TokenTransferInput{ - TokenTransfer: fftypes.TokenTransfer{ + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "magic-tokens" + burn.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.BurnTokens(context.Background(), "ns1", burn, false) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestBurnTokensUnknownConnectorSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "" + burn.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.BurnTokens(context.Background(), "ns1", burn, false) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestBurnTokenUnknownConnectorNoConnectors(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "" + burn.Pool = "pool1" + + am.tokens = make(map[string]tokens.Plugin) + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.BurnTokens(context.Background(), "ns1", burn, false) + assert.Regexp(t, "FF10291", err) +} + +func TestBurnTokenUnknownConnectorMultipleConnectors(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "" + burn.Pool = "pool1" + + am.tokens["magic-tokens"] = nil + am.tokens["magic-tokens2"] = nil + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.BurnTokens(context.Background(), "ns1", burn, false) + assert.Regexp(t, "FF10291", err) +} + +func TestBurnTokenUnknownConnectorBadNamespace(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "" + burn.Pool = "pool1" + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.BurnTokens(context.Background(), "", burn, false) + assert.Regexp(t, "FF10131", err) +} + +func TestBurnTokensIdentityFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "magic-tokens" + burn.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + + _, err := am.BurnTokens(context.Background(), "ns1", burn, false) + assert.EqualError(t, err, "pop") +} + +func TestBurnTokensConfirm(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "magic-tokens" + burn.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + msa := am.syncasync.(*syncasyncmocks.Bridge) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + msa.On("WaitForTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send(context.Background()) + }). + Return(&fftypes.TokenTransfer{}, nil) + + _, err := am.BurnTokens(context.Background(), "ns1", burn, true) + assert.NoError(t, err) + + mdi.AssertExpectations(t) + mdm.AssertExpectations(t) + msa.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestBurnTokenUnknownPoolSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "magic-tokens" + burn.Pool = "" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + fb := database.TokenPoolQueryFactory.NewFilter(context.Background()) + f := fb.And() + f.Limit(1).Count(true) + tokenPools := []*fftypes.TokenPool{ + { + Name: "pool1", + }, + } + totalCount := int64(1) + filterResult := &database.FilterResult{ + TotalCount: &totalCount, + } + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPools", context.Background(), mock.MatchedBy((func(f database.AndFilter) bool { + info, _ := f.Finalize() + return info.Count && info.Limit == 1 + }))).Return(tokenPools, filterResult, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.BurnTokens(context.Background(), "ns1", burn, false) + assert.NoError(t, err) +} + +func TestBurnTokenUnknownPoolNoPools(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "magic-tokens" + burn.Pool = "" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + fb := database.TokenPoolQueryFactory.NewFilter(context.Background()) + f := fb.And() + f.Limit(1).Count(true) + tokenPools := []*fftypes.TokenPool{} + totalCount := int64(0) + filterResult := &database.FilterResult{ + TotalCount: &totalCount, + } + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPools", context.Background(), mock.MatchedBy((func(f database.AndFilter) bool { + info, _ := f.Finalize() + return info.Count && info.Limit == 1 + }))).Return(tokenPools, filterResult, nil) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.BurnTokens(context.Background(), "ns1", burn, false) + assert.Regexp(t, "FF10291", err) +} + +func TestBurnTokenUnknownPoolMultiplePools(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "magic-tokens" + burn.Pool = "" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + fb := database.TokenPoolQueryFactory.NewFilter(context.Background()) + f := fb.And() + f.Limit(1).Count(true) + tokenPools := []*fftypes.TokenPool{ + { + Name: "pool1", + }, + { + Name: "pool2", + }, + } + totalCount := int64(2) + filterResult := &database.FilterResult{ + TotalCount: &totalCount, + } + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPools", context.Background(), mock.MatchedBy((func(f database.AndFilter) bool { + info, _ := f.Finalize() + return info.Count && info.Limit == 1 + }))).Return(tokenPools, filterResult, nil) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.BurnTokens(context.Background(), "ns1", burn, false) + assert.Regexp(t, "FF10291", err) +} + +func TestBurnTokenUnknownPoolBadNamespace(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + burn.Connector = "magic-tokens" + burn.Pool = "" + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.BurnTokens(context.Background(), "", burn, false) + assert.Regexp(t, "FF10131", err) +} + +func TestBurnTokensByTypeSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestBurnTokensByTypeIdentityFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + + _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, false) + assert.EqualError(t, err, "pop") +} + +func TestBurnTokensByTypeConfirm(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + msa := am.syncasync.(*syncasyncmocks.Bridge) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + msa.On("WaitForTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send(context.Background()) + }). + Return(&fftypes.TokenTransfer{}, nil) + + _, err := am.BurnTokensByType(context.Background(), "ns1", "magic-tokens", "pool1", burn, true) + assert.NoError(t, err) + + mdi.AssertExpectations(t) + mdm.AssertExpectations(t) + msa.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestTransferTokensSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestTransferTokensUnknownConnectorSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "" + transfer.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestTransferTokenUnknownConnectorNoConnectors(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "" + transfer.Pool = "pool1" + + am.tokens = make(map[string]tokens.Plugin) + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.Regexp(t, "FF10291", err) +} + +func TestTransferTokenUnknownConnectorMultipleConnectors(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "" + transfer.Pool = "pool1" + + am.tokens["magic-tokens"] = nil + am.tokens["magic-tokens2"] = nil + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.Regexp(t, "FF10291", err) +} + +func TestTransferTokenUnknownConnectorBadNamespace(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "" + transfer.Pool = "pool1" + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.TransferTokens(context.Background(), "", transfer, false) + assert.Regexp(t, "FF10131", err) +} + +func TestTransferTokenUnknownPoolSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + fb := database.TokenPoolQueryFactory.NewFilter(context.Background()) + f := fb.And() + f.Limit(1).Count(true) + tokenPools := []*fftypes.TokenPool{ + { + Name: "pool1", + }, + } + totalCount := int64(1) + filterResult := &database.FilterResult{ + TotalCount: &totalCount, + } + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPools", context.Background(), mock.MatchedBy((func(f database.AndFilter) bool { + info, _ := f.Finalize() + return info.Count && info.Limit == 1 + }))).Return(tokenPools, filterResult, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.NoError(t, err) +} + +func TestTransferTokenUnknownPoolNoPools(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + fb := database.TokenPoolQueryFactory.NewFilter(context.Background()) + f := fb.And() + f.Limit(1).Count(true) + tokenPools := []*fftypes.TokenPool{} + totalCount := int64(0) + filterResult := &database.FilterResult{ + TotalCount: &totalCount, + } + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPools", context.Background(), mock.MatchedBy((func(f database.AndFilter) bool { + info, _ := f.Finalize() + return info.Count && info.Limit == 1 + }))).Return(tokenPools, filterResult, nil) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.Regexp(t, "FF10291", err) +} + +func TestTransferTokenUnknownPoolMultiplePools(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + fb := database.TokenPoolQueryFactory.NewFilter(context.Background()) + f := fb.And() + f.Limit(1).Count(true) + tokenPools := []*fftypes.TokenPool{ + { + Name: "pool1", + }, + { + Name: "pool2", + }, + } + totalCount := int64(2) + filterResult := &database.FilterResult{ + TotalCount: &totalCount, + } + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPools", context.Background(), mock.MatchedBy((func(f database.AndFilter) bool { + info, _ := f.Finalize() + return info.Count && info.Limit == 1 + }))).Return(tokenPools, filterResult, nil) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.Regexp(t, "FF10291", err) +} + +func TestTransferTokenUnknownPoolBadNamespace(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "" + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.TransferTokens(context.Background(), "", transfer, false) + assert.Regexp(t, "FF10131", err) +} + +func TestTransferTokensIdentityFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.EqualError(t, err, "pop") +} + +func TestTransferTokensNoFromOrTo(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{} + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.Regexp(t, "FF10280", err) + + mim.AssertExpectations(t) +} + +func TestTransferTokensTransactionFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(fmt.Errorf("pop")) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.EqualError(t, err, "pop") + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) +} + +func TestTransferTokensWithBroadcastMessage(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + hash := fftypes.NewRandB32() + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Hash: hash, + }, + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mbm := am.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) + mms.On("Prepare", context.Background()).Return(nil) + mms.On("Send", context.Background()).Return(nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.NoError(t, err) + assert.Equal(t, *hash, *transfer.MessageHash) + + mbm.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestTransferTokensWithBroadcastMessageFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" + + mim := am.identity.(*identitymanagermocks.Manager) + mbm := am.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) + mms.On("Prepare", context.Background()).Return(nil) + mms.On("Send", context.Background()).Return(fmt.Errorf("pop")) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.EqualError(t, err, "pop") + + mbm.AssertExpectations(t) + mim.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestTransferTokensWithBroadcastPrepareFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" + + mim := am.identity.(*identitymanagermocks.Manager) + mbm := am.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) + mms.On("Prepare", context.Background()).Return(fmt.Errorf("pop")) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.EqualError(t, err, "pop") + + mbm.AssertExpectations(t) + mim.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestTransferTokensWithPrivateMessage(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + hash := fftypes.NewRandB32() + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ From: "A", To: "B", }, + Message: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Type: fftypes.MessageTypeTransferPrivate, + }, + Hash: hash, + }, + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, } transfer.Amount.Int().SetInt64(5) transfer.Connector = "magic-tokens" @@ -393,6 +1475,8 @@ func TestTransferTokensSuccess(t *testing.T) { mdi := am.database.(*databasemocks.Plugin) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) + mpm := am.messaging.(*privatemessagingmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) @@ -400,13 +1484,214 @@ func TestTransferTokensSuccess(t *testing.T) { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mpm.On("NewMessage", "ns1", transfer.Message).Return(mms) + mms.On("Prepare", context.Background()).Return(nil) + mms.On("Send", context.Background()).Return(nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.NoError(t, err) + assert.Equal(t, *hash, *transfer.MessageHash) + + mpm.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestTransferTokensWithInvalidMessage(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Type: fftypes.MessageTypeDefinition, + }, + }, + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" + + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) + assert.Regexp(t, "FF10287", err) + + mim.AssertExpectations(t) +} + +func TestTransferTokensConfirm(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + msa := am.syncasync.(*syncasyncmocks.Bridge) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + msa.On("WaitForTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send(context.Background()) + }). + Return(&fftypes.TokenTransfer{}, nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, true) + assert.NoError(t, err) + + mdi.AssertExpectations(t) + mdm.AssertExpectations(t) + msa.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestTransferTokensWithBroadcastConfirm(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + hash := fftypes.NewRandB32() + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Hash: hash, + }, + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mbm := am.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + msa := am.syncasync.(*syncasyncmocks.Bridge) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) + mms.On("Prepare", context.Background()).Return(nil) + mms.On("SendAndWait", context.Background()).Return(nil) + msa.On("WaitForTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send(context.Background()) + }). + Return(&transfer.TokenTransfer, nil) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, true) assert.NoError(t, err) + assert.Equal(t, *hash, *transfer.MessageHash) + + mbm.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) + mms.AssertExpectations(t) + msa.AssertExpectations(t) +} + +func TestTransferTokensWithBroadcastConfirmTransferFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + hash := fftypes.NewRandB32() + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Hash: hash, + }, + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + transfer.Connector = "magic-tokens" + transfer.Pool = "pool1" + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mbm := am.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + msa := am.syncasync.(*syncasyncmocks.Bridge) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) + mms.On("Prepare", context.Background()).Return(nil) + msa.On("WaitForTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send(context.Background()) + }). + Return(nil, fmt.Errorf("pop")) + + _, err := am.TransferTokens(context.Background(), "ns1", transfer, true) + assert.EqualError(t, err, "pop") + mbm.AssertExpectations(t) mim.AssertExpectations(t) mdi.AssertExpectations(t) mti.AssertExpectations(t) + mms.AssertExpectations(t) + msa.AssertExpectations(t) } func TestTransferTokensByTypeSuccess(t *testing.T) { From 20047a8139dc456422354f83309e32e700c90100 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Thu, 28 Oct 2021 16:55:45 -0400 Subject: [PATCH 4/7] post-tokens moving placement of pool key to TokenTransferInput Signed-off-by: David Echelberger --- pkg/fftypes/tokentransfer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fftypes/tokentransfer.go b/pkg/fftypes/tokentransfer.go index 54ec36c753..41276f8fca 100644 --- a/pkg/fftypes/tokentransfer.go +++ b/pkg/fftypes/tokentransfer.go @@ -35,7 +35,6 @@ type TokenTransfer struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` Amount BigInt `json:"amount"` - Pool string `json:"pool,omitempty"` ProtocolID string `json:"protocolId,omitempty"` MessageHash *Bytes32 `json:"messageHash,omitempty"` Created *FFTime `json:"created,omitempty"` @@ -45,4 +44,5 @@ type TokenTransfer struct { type TokenTransferInput struct { TokenTransfer Message *MessageInOut `json:"message,omitempty"` + Pool string `json:"pool,omitempty"` } From 79fe074d6418ebb67193e65dda7d2fc11f4a7a31 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Thu, 28 Oct 2021 17:07:20 -0400 Subject: [PATCH 5/7] post-tokens swagger Signed-off-by: David Echelberger --- docs/swagger/swagger.yaml | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index be65ba8e36..73ef8ba203 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -4759,8 +4759,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -4796,8 +4794,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -4982,8 +4978,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -5019,8 +5013,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -5185,8 +5177,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -5371,8 +5361,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -5408,8 +5396,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -5694,8 +5680,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -5731,8 +5715,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -5938,8 +5920,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -5975,8 +5955,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -6340,8 +6318,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -6514,8 +6490,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -6551,8 +6525,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: @@ -6616,8 +6588,6 @@ paths: messageHash: {} namespace: type: string - pool: - type: string poolProtocolId: type: string protocolId: From 45edc66da9cb195b19e7a93da4b2cad2932b69ff Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Fri, 29 Oct 2021 10:16:15 -0400 Subject: [PATCH 6/7] post-tokens renaming POST tokensByType Signed-off-by: David Echelberger --- docs/swagger/swagger.yaml | 2 +- ...ol.go => route_post_token_pool_by_type.go} | 6 ++-- ... => route_post_token_pool_by_type_test.go} | 4 +-- internal/apiserver/routes.go | 2 +- internal/assets/manager.go | 3 +- internal/assets/token_pool.go | 2 +- internal/assets/token_pool_test.go | 32 +++++++++---------- mocks/assetmocks/manager.go | 4 +-- 8 files changed, 28 insertions(+), 27 deletions(-) rename internal/apiserver/{route_post_token_pool.go => route_post_token_pool_by_type.go} (90%) rename internal/apiserver/{route_post_token_pool_test.go => route_post_token_pool_by_type_test.go} (89%) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 73ef8ba203..d7adefb2ab 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -4282,7 +4282,7 @@ paths: description: "" post: description: 'TODO: Description' - operationId: postTokenPool + operationId: postTokenPoolByType parameters: - description: 'TODO: Description' in: path diff --git a/internal/apiserver/route_post_token_pool.go b/internal/apiserver/route_post_token_pool_by_type.go similarity index 90% rename from internal/apiserver/route_post_token_pool.go rename to internal/apiserver/route_post_token_pool_by_type.go index e92c87eb65..21cdd84a55 100644 --- a/internal/apiserver/route_post_token_pool.go +++ b/internal/apiserver/route_post_token_pool_by_type.go @@ -26,8 +26,8 @@ import ( "github.com/hyperledger/firefly/pkg/fftypes" ) -var postTokenPool = &oapispec.Route{ - Name: "postTokenPool", +var postTokenPoolByType = &oapispec.Route{ + Name: "postTokenPoolByType", Path: "namespaces/{ns}/tokens/{type}/pools", Method: http.MethodPost, PathParams: []*oapispec.PathParam{ @@ -46,6 +46,6 @@ var postTokenPool = &oapispec.Route{ JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - return r.Or.Assets().CreateTokenPool(r.Ctx, r.PP["ns"], r.PP["type"], r.Input.(*fftypes.TokenPool), waitConfirm) + return r.Or.Assets().CreateTokenPoolByType(r.Ctx, r.PP["ns"], r.PP["type"], r.Input.(*fftypes.TokenPool), waitConfirm) }, } diff --git a/internal/apiserver/route_post_token_pool_test.go b/internal/apiserver/route_post_token_pool_by_type_test.go similarity index 89% rename from internal/apiserver/route_post_token_pool_test.go rename to internal/apiserver/route_post_token_pool_by_type_test.go index bf7c99b9f0..96eed311b3 100644 --- a/internal/apiserver/route_post_token_pool_test.go +++ b/internal/apiserver/route_post_token_pool_by_type_test.go @@ -28,7 +28,7 @@ import ( "github.com/stretchr/testify/mock" ) -func TestPostTokenPool(t *testing.T) { +func TestPostTokenPoolByType(t *testing.T) { o, r := newTestAPIServer() mam := &assetmocks.Manager{} o.On("Assets").Return(mam) @@ -39,7 +39,7 @@ func TestPostTokenPool(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mam.On("CreateTokenPool", mock.Anything, "ns1", "tok1", mock.AnythingOfType("*fftypes.TokenPool"), false). + mam.On("CreateTokenPoolByType", mock.Anything, "ns1", "tok1", mock.AnythingOfType("*fftypes.TokenPool"), false). Return(&fftypes.TokenPool{}, nil) r.ServeHTTP(res, req) diff --git a/internal/apiserver/routes.go b/internal/apiserver/routes.go index 93ac467dee..e2c035ce86 100644 --- a/internal/apiserver/routes.go +++ b/internal/apiserver/routes.go @@ -77,7 +77,7 @@ var routes = []*oapispec.Route{ getTxnOps, getTxns, - postTokenPool, + postTokenPoolByType, getTokenPools, getTokenPoolsByType, getTokenPoolByNameOrID, diff --git a/internal/assets/manager.go b/internal/assets/manager.go index 0fadc06590..1ae99abe73 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -35,7 +35,8 @@ import ( ) type Manager interface { - CreateTokenPool(ctx context.Context, ns, connector string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) + // 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) diff --git a/internal/assets/token_pool.go b/internal/assets/token_pool.go index 1b535e7d17..453d4d3c2a 100644 --- a/internal/assets/token_pool.go +++ b/internal/assets/token_pool.go @@ -49,7 +49,7 @@ func retrieveTokenPoolCreateInputs(ctx context.Context, op *fftypes.Operation, p return nil } -func (am *assetManager) CreateTokenPool(ctx context.Context, ns string, connector string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) { +func (am *assetManager) CreateTokenPoolByType(ctx context.Context, ns string, connector string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) { if err := am.data.VerifyNamespaceExists(ctx, ns); err != nil { return nil, err } diff --git a/internal/assets/token_pool_test.go b/internal/assets/token_pool_test.go index 8eea63f1e5..017d30e424 100644 --- a/internal/assets/token_pool_test.go +++ b/internal/assets/token_pool_test.go @@ -31,18 +31,18 @@ import ( "github.com/stretchr/testify/mock" ) -func TestCreateTokenPoolBadNamespace(t *testing.T) { +func TestCreateTokenPoolByTypeBadNamespace(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() mdm := am.data.(*datamocks.Manager) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(fmt.Errorf("pop")) - _, err := am.CreateTokenPool(context.Background(), "ns1", "test", &fftypes.TokenPool{}, false) + _, err := am.CreateTokenPoolByType(context.Background(), "ns1", "test", &fftypes.TokenPool{}, false) assert.EqualError(t, err, "pop") } -func TestCreateTokenPoolIdentityFail(t *testing.T) { +func TestCreateTokenPoolByTypeIdentityFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -51,11 +51,11 @@ func TestCreateTokenPoolIdentityFail(t *testing.T) { mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) - _, err := am.CreateTokenPool(context.Background(), "ns1", "test", &fftypes.TokenPool{}, false) + _, err := am.CreateTokenPoolByType(context.Background(), "ns1", "test", &fftypes.TokenPool{}, false) assert.EqualError(t, err, "pop") } -func TestCreateTokenPoolBadConnector(t *testing.T) { +func TestCreateTokenPoolByTypeBadConnector(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -64,11 +64,11 @@ func TestCreateTokenPoolBadConnector(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) - _, err := am.CreateTokenPool(context.Background(), "ns1", "bad", &fftypes.TokenPool{}, false) + _, err := am.CreateTokenPoolByType(context.Background(), "ns1", "bad", &fftypes.TokenPool{}, false) assert.Regexp(t, "FF10272", err) } -func TestCreateTokenPoolFail(t *testing.T) { +func TestCreateTokenPoolByTypeFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -84,11 +84,11 @@ func TestCreateTokenPoolFail(t *testing.T) { mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - _, err := am.CreateTokenPool(context.Background(), "ns1", "magic-tokens", &fftypes.TokenPool{}, false) + _, err := am.CreateTokenPoolByType(context.Background(), "ns1", "magic-tokens", &fftypes.TokenPool{}, false) assert.Regexp(t, "pop", err) } -func TestCreateTokenPoolTransactionFail(t *testing.T) { +func TestCreateTokenPoolByTypeTransactionFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -99,11 +99,11 @@ func TestCreateTokenPoolTransactionFail(t *testing.T) { mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.Anything, false).Return(fmt.Errorf("pop")) - _, err := am.CreateTokenPool(context.Background(), "ns1", "magic-tokens", &fftypes.TokenPool{}, false) + _, err := am.CreateTokenPoolByType(context.Background(), "ns1", "magic-tokens", &fftypes.TokenPool{}, false) assert.Regexp(t, "pop", err) } -func TestCreateTokenPoolOperationFail(t *testing.T) { +func TestCreateTokenPoolByTypeOperationFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -117,11 +117,11 @@ func TestCreateTokenPoolOperationFail(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(fmt.Errorf("pop")) - _, err := am.CreateTokenPool(context.Background(), "ns1", "magic-tokens", &fftypes.TokenPool{}, false) + _, err := am.CreateTokenPoolByType(context.Background(), "ns1", "magic-tokens", &fftypes.TokenPool{}, false) assert.Regexp(t, "pop", err) } -func TestCreateTokenPoolSuccess(t *testing.T) { +func TestCreateTokenPoolByTypeSuccess(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -137,11 +137,11 @@ func TestCreateTokenPoolSuccess(t *testing.T) { }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - _, err := am.CreateTokenPool(context.Background(), "ns1", "magic-tokens", &fftypes.TokenPool{}, false) + _, err := am.CreateTokenPoolByType(context.Background(), "ns1", "magic-tokens", &fftypes.TokenPool{}, false) assert.NoError(t, err) } -func TestCreateTokenPoolConfirm(t *testing.T) { +func TestCreateTokenPoolByTypeConfirm(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -164,7 +164,7 @@ func TestCreateTokenPoolConfirm(t *testing.T) { }). Return(nil, nil) - _, err := am.CreateTokenPool(context.Background(), "ns1", "magic-tokens", &fftypes.TokenPool{}, true) + _, err := am.CreateTokenPoolByType(context.Background(), "ns1", "magic-tokens", &fftypes.TokenPool{}, true) assert.NoError(t, err) } diff --git a/mocks/assetmocks/manager.go b/mocks/assetmocks/manager.go index ec7ccc9d7e..33e8975399 100644 --- a/mocks/assetmocks/manager.go +++ b/mocks/assetmocks/manager.go @@ -66,8 +66,8 @@ func (_m *Manager) BurnTokensByType(ctx context.Context, ns string, connector st return r0, r1 } -// CreateTokenPool provides a mock function with given fields: ctx, ns, connector, pool, waitConfirm -func (_m *Manager) CreateTokenPool(ctx context.Context, ns string, connector string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) { +// CreateTokenPoolByType provides a mock function with given fields: ctx, ns, connector, pool, waitConfirm +func (_m *Manager) CreateTokenPoolByType(ctx context.Context, ns string, connector string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) { ret := _m.Called(ctx, ns, connector, pool, waitConfirm) var r0 *fftypes.TokenPool From 659c1ace60db6a4c8bbde1c1e6aede244ca10e32 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Fri, 29 Oct 2021 11:35:39 -0400 Subject: [PATCH 7/7] post-tokens POST /tokens/pools Signed-off-by: David Echelberger --- docs/swagger/swagger.yaml | 118 ++++++++++++ internal/apiserver/route_post_token_pool.go | 50 +++++ .../apiserver/route_post_token_pool_test.go | 47 +++++ internal/apiserver/routes.go | 1 + internal/assets/manager.go | 26 ++- internal/assets/token_pool.go | 25 +++ internal/assets/token_pool_test.go | 175 ++++++++++++++++++ internal/assets/token_transfer.go | 24 --- mocks/assetmocks/manager.go | 23 +++ 9 files changed, 464 insertions(+), 25 deletions(-) create mode 100644 internal/apiserver/route_post_token_pool.go create mode 100644 internal/apiserver/route_post_token_pool_test.go diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index d7adefb2ab..75042757fe 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -6123,6 +6123,124 @@ paths: description: Success default: description: "" + post: + description: 'TODO: Description' + operationId: postTokenPool + parameters: + - description: 'TODO: Description' + in: path + name: ns + required: true + schema: + example: default + type: string + - description: When true the HTTP request blocks until the message is confirmed + in: query + name: confirm + schema: + 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 + requestBody: + content: + application/json: + schema: + properties: + config: + additionalProperties: {} + type: object + connector: + type: string + key: + type: string + name: + type: string + symbol: + type: string + type: + enum: + - fungible + - nonfungible + type: string + type: object + responses: + "200": + content: + application/json: + schema: + properties: + config: + additionalProperties: {} + type: object + connector: + type: string + created: {} + id: {} + key: + type: string + message: {} + name: + type: string + namespace: + type: string + protocolId: + type: string + standard: + type: string + symbol: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + type: string + type: object + description: Success + "202": + content: + application/json: + schema: + properties: + config: + additionalProperties: {} + type: object + connector: + type: string + created: {} + id: {} + key: + type: string + message: {} + name: + type: string + namespace: + type: string + protocolId: + type: string + standard: + type: string + symbol: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + type: string + type: object + description: Success + default: + description: "" /namespaces/{ns}/tokens/pools/{nameOrID}: get: description: 'TODO: Description' diff --git a/internal/apiserver/route_post_token_pool.go b/internal/apiserver/route_post_token_pool.go new file mode 100644 index 0000000000..5892ab8d4c --- /dev/null +++ b/internal/apiserver/route_post_token_pool.go @@ -0,0 +1,50 @@ +// 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" + "strings" + + "github.com/hyperledger/firefly/internal/config" + "github.com/hyperledger/firefly/internal/i18n" + "github.com/hyperledger/firefly/internal/oapispec" + "github.com/hyperledger/firefly/pkg/fftypes" +) + +var postTokenPool = &oapispec.Route{ + Name: "postTokenPool", + Path: "namespaces/{ns}/tokens/pools", + Method: http.MethodPost, + PathParams: []*oapispec.PathParam{ + {Name: "ns", ExampleFromConf: config.NamespacesDefault, Description: i18n.MsgTBD}, + }, + QueryParams: []*oapispec.QueryParam{ + {Name: "confirm", Description: i18n.MsgConfirmQueryParam, IsBool: true}, + }, + FilterFactory: nil, + Description: i18n.MsgTBD, + JSONInputValue: func() interface{} { return &fftypes.TokenPool{} }, + JSONInputMask: []string{"ID", "Namespace", "Standard", "ProtocolID", "TX", "Message", "Created"}, + JSONOutputValue: func() interface{} { return &fftypes.TokenPool{} }, + JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK}, + JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { + waitConfirm := strings.EqualFold(r.QP["confirm"], "true") + r.SuccessStatus = syncRetcode(waitConfirm) + return r.Or.Assets().CreateTokenPool(r.Ctx, r.PP["ns"], r.Input.(*fftypes.TokenPool), waitConfirm) + }, +} diff --git a/internal/apiserver/route_post_token_pool_test.go b/internal/apiserver/route_post_token_pool_test.go new file mode 100644 index 0000000000..86262505fb --- /dev/null +++ b/internal/apiserver/route_post_token_pool_test.go @@ -0,0 +1,47 @@ +// 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 ( + "bytes" + "encoding/json" + "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 TestPostTokenPool(t *testing.T) { + o, r := newTestAPIServer() + mam := &assetmocks.Manager{} + o.On("Assets").Return(mam) + input := fftypes.TokenPool{} + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(&input) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/tokens/pools", &buf) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + mam.On("CreateTokenPool", mock.Anything, "ns1", mock.AnythingOfType("*fftypes.TokenPool"), false). + Return(&fftypes.TokenPool{}, nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 202, res.Result().StatusCode) +} diff --git a/internal/apiserver/routes.go b/internal/apiserver/routes.go index e2c035ce86..1fc28b3f2e 100644 --- a/internal/apiserver/routes.go +++ b/internal/apiserver/routes.go @@ -77,6 +77,7 @@ var routes = []*oapispec.Route{ getTxnOps, getTxns, + postTokenPool, postTokenPoolByType, getTokenPools, getTokenPoolsByType, diff --git a/internal/assets/manager.go b/internal/assets/manager.go index 1ae99abe73..be06c3a440 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -35,7 +35,7 @@ import ( ) type Manager interface { - // CreateTokenPool(ctx context.Context, ns string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) + 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) @@ -149,3 +149,27 @@ func (am *assetManager) Start() error { func (am *assetManager) WaitStop() { // No go routines } + +func (am *assetManager) getTokenConnectorName(ctx context.Context, ns string) (string, error) { + tokenConnectors, err := am.GetTokenConnectors(ctx, ns) + if err != nil { + return "", err + } + if len(tokenConnectors) != 1 { + return "", i18n.NewError(ctx, i18n.MsgFieldNotSpecified, "connector") + } + return tokenConnectors[0].Name, nil +} + +func (am *assetManager) getTokenPoolName(ctx context.Context, ns string) (string, error) { + f := database.TokenPoolQueryFactory.NewFilter(ctx).And() + f.Limit(1).Count(true) + tokenPools, fr, err := am.GetTokenPools(ctx, ns, f) + if err != nil { + return "", err + } + if *fr.TotalCount != 1 { + return "", i18n.NewError(ctx, i18n.MsgFieldNotSpecified, "pool") + } + return tokenPools[0].Name, nil +} diff --git a/internal/assets/token_pool.go b/internal/assets/token_pool.go index 453d4d3c2a..c972df99c3 100644 --- a/internal/assets/token_pool.go +++ b/internal/assets/token_pool.go @@ -49,6 +49,31 @@ func retrieveTokenPoolCreateInputs(ctx context.Context, op *fftypes.Operation, p return nil } +func (am *assetManager) CreateTokenPool(ctx context.Context, ns string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) { + if err := am.data.VerifyNamespaceExists(ctx, ns); err != nil { + return nil, err + } + pool.ID = fftypes.NewUUID() + pool.Namespace = ns + + if pool.Connector == "" { + connector, err := am.getTokenConnectorName(ctx, ns) + if err != nil { + return nil, err + } + pool.Connector = connector + } + + if pool.Key == "" { + org, err := am.identity.GetLocalOrganization(ctx) + if err != nil { + return nil, err + } + pool.Key = org.Identity + } + return am.createTokenPoolInternal(ctx, pool, waitConfirm) +} + func (am *assetManager) CreateTokenPoolByType(ctx context.Context, ns string, connector string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) { if err := am.data.VerifyNamespaceExists(ctx, ns); err != nil { return nil, err diff --git a/internal/assets/token_pool_test.go b/internal/assets/token_pool_test.go index 017d30e424..87e9db632b 100644 --- a/internal/assets/token_pool_test.go +++ b/internal/assets/token_pool_test.go @@ -27,10 +27,185 @@ import ( "github.com/hyperledger/firefly/mocks/tokenmocks" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" + "github.com/hyperledger/firefly/pkg/tokens" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) +func TestCreateTokenPoolBadNamespace(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mdm := am.data.(*datamocks.Manager) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(fmt.Errorf("pop")) + + _, err := am.CreateTokenPool(context.Background(), "ns1", &fftypes.TokenPool{}, false) + assert.EqualError(t, err, "pop") +} + +func TestCreateTokenPoolIdentityFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mdm := am.data.(*datamocks.Manager) + mim := am.identity.(*identitymanagermocks.Manager) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + mim.On("GetLocalOrganization", context.Background()).Return(nil, fmt.Errorf("pop")) + + _, err := am.CreateTokenPool(context.Background(), "ns1", &fftypes.TokenPool{}, false) + assert.EqualError(t, err, "pop") +} + +func TestCreateTokenPoolFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenPool + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + + _, err := am.CreateTokenPool(context.Background(), "ns1", &fftypes.TokenPool{}, false) + assert.Regexp(t, "pop", err) +} + +func TestCreateTokenPoolTransactionFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.Anything, false).Return(fmt.Errorf("pop")) + + _, err := am.CreateTokenPool(context.Background(), "ns1", &fftypes.TokenPool{}, false) + assert.Regexp(t, "pop", err) +} + +func TestCreateTokenPoolOperationFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenPool + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(fmt.Errorf("pop")) + + _, err := am.CreateTokenPool(context.Background(), "ns1", &fftypes.TokenPool{}, false) + assert.Regexp(t, "pop", err) +} + +func TestCreateTokenPoolSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + pool := &fftypes.TokenPool{} + pool.Connector = "test" + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenPool + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.CreateTokenPool(context.Background(), "ns1", &fftypes.TokenPool{}, false) + assert.NoError(t, err) +} + +func TestCreateTokenPoolUnknownConnectorSuccess(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenPool + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + + _, err := am.CreateTokenPool(context.Background(), "ns1", &fftypes.TokenPool{}, false) + assert.NoError(t, err) +} + +func TestCreateTokenPoolUnknownConnectorNoConnectors(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + am.tokens = make(map[string]tokens.Plugin) + + mdm := am.data.(*datamocks.Manager) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + + _, err := am.CreateTokenPool(context.Background(), "ns1", &fftypes.TokenPool{}, false) + assert.Regexp(t, "FF10291", err) +} + +func TestCreateTokenPoolUnknownConnectorMultipleConnectors(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + am.tokens["magic-tokens"] = nil + am.tokens["magic-tokens2"] = nil + + mdm := am.data.(*datamocks.Manager) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + + _, err := am.CreateTokenPool(context.Background(), "ns1", &fftypes.TokenPool{}, false) + assert.Regexp(t, "FF10291", err) +} + +func TestCreateTokenPoolConfirm(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + msa := am.syncasync.(*syncasyncmocks.Bridge) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil).Times(2) + mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything).Return(nil).Times(1) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenPool + }), false).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil).Times(1) + msa.On("WaitForTokenPool", context.Background(), "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send(context.Background()) + }). + Return(nil, nil) + + _, err := am.CreateTokenPool(context.Background(), "ns1", &fftypes.TokenPool{}, true) + assert.NoError(t, err) +} + func TestCreateTokenPoolByTypeBadNamespace(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() diff --git a/internal/assets/token_transfer.go b/internal/assets/token_transfer.go index 911aa2d389..78d5fd61f7 100644 --- a/internal/assets/token_transfer.go +++ b/internal/assets/token_transfer.go @@ -424,27 +424,3 @@ func (s *transferSender) buildTransferMessage(ctx context.Context, ns string, in return nil, i18n.NewError(ctx, i18n.MsgInvalidMessageType, allowedTypes) } } - -func (am *assetManager) getTokenConnectorName(ctx context.Context, ns string) (string, error) { - tokenConnectors, err := am.GetTokenConnectors(ctx, ns) - if err != nil { - return "", err - } - if len(tokenConnectors) != 1 { - return "", i18n.NewError(ctx, i18n.MsgFieldNotSpecified, "connector") - } - return tokenConnectors[0].Name, nil -} - -func (am *assetManager) getTokenPoolName(ctx context.Context, ns string) (string, error) { - f := database.TokenPoolQueryFactory.NewFilter(ctx).And() - f.Limit(1).Count(true) - tokenPools, fr, err := am.GetTokenPools(ctx, ns, f) - if err != nil { - return "", err - } - if *fr.TotalCount != 1 { - return "", i18n.NewError(ctx, i18n.MsgFieldNotSpecified, "pool") - } - return tokenPools[0].Name, nil -} diff --git a/mocks/assetmocks/manager.go b/mocks/assetmocks/manager.go index 33e8975399..1734bad27e 100644 --- a/mocks/assetmocks/manager.go +++ b/mocks/assetmocks/manager.go @@ -66,6 +66,29 @@ func (_m *Manager) BurnTokensByType(ctx context.Context, ns string, connector st return r0, r1 } +// CreateTokenPool provides a mock function with given fields: ctx, ns, pool, waitConfirm +func (_m *Manager) CreateTokenPool(ctx context.Context, ns string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) { + ret := _m.Called(ctx, ns, pool, waitConfirm) + + var r0 *fftypes.TokenPool + if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.TokenPool, bool) *fftypes.TokenPool); ok { + r0 = rf(ctx, ns, pool, waitConfirm) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*fftypes.TokenPool) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, *fftypes.TokenPool, bool) error); ok { + r1 = rf(ctx, ns, pool, waitConfirm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // CreateTokenPoolByType provides a mock function with given fields: ctx, ns, connector, pool, waitConfirm func (_m *Manager) CreateTokenPoolByType(ctx context.Context, ns string, connector string, pool *fftypes.TokenPool, waitConfirm bool) (*fftypes.TokenPool, error) { ret := _m.Called(ctx, ns, connector, pool, waitConfirm)