diff --git a/domains/noto/internal/msgs/en_errors.go b/domains/noto/internal/msgs/en_errors.go index 137d1615f..18572b924 100644 --- a/domains/noto/internal/msgs/en_errors.go +++ b/domains/noto/internal/msgs/en_errors.go @@ -59,4 +59,5 @@ var ( MsgSignatureDoesNotMatch = ffe("PD200017", "Signature for '%s' did not match: expected=%s actual=%s") MsgStateWrongOwner = ffe("PD200018", "State '%s' is not owned by '%s'") MsgUnrecognizedEndorsement = ffe("PD200019", "Unrecognized endorsement request: %s") + MsgDuplicateStateInList = ffe("PD200020", "Duplicate state in list %s[%d] (%s)") ) diff --git a/domains/noto/internal/noto/e2e_noto_test.go b/domains/noto/internal/noto/e2e_noto_test.go index 8e2424d87..e09cf97c0 100644 --- a/domains/noto/internal/noto/e2e_noto_test.go +++ b/domains/noto/internal/noto/e2e_noto_test.go @@ -190,6 +190,10 @@ func TestNoto(t *testing.T) { assert.ErrorContains(t, rpcerr.Error(), "PD200009") assert.True(t, boolResult) + coins, err = noto.FindCoins(ctx, "{}") + require.NoError(t, err) + require.Len(t, coins, 1) + log.L(ctx).Infof("Transfer 150 from notary (should fail)") rpcerr = rpc.CallRPC(ctx, &boolResult, "testbed_invoke", &tktypes.PrivateContractInvoke{ From: notaryName, @@ -203,6 +207,10 @@ func TestNoto(t *testing.T) { require.NotNil(t, rpcerr) assert.ErrorContains(t, rpcerr.Error(), "PD200005") + coins, err = noto.FindCoins(ctx, "{}") + require.NoError(t, err) + require.Len(t, coins, 1) + log.L(ctx).Infof("Transfer 50 from notary to recipient1") rpcerr = rpc.CallRPC(ctx, &boolResult, "testbed_invoke", &tktypes.PrivateContractInvoke{ From: notaryName, diff --git a/domains/noto/internal/noto/noto.go b/domains/noto/internal/noto/noto.go index bd2d2f278..ae9610eb0 100644 --- a/domains/noto/internal/noto/noto.go +++ b/domains/noto/internal/noto/noto.go @@ -289,19 +289,24 @@ func (n *Noto) recoverSignature(ctx context.Context, payload ethtypes.HexBytes0x func (n *Noto) parseCoinList(ctx context.Context, label string, states []*pb.EndorsableState) ([]*types.NotoCoin, []*pb.StateRef, *big.Int, error) { var err error + statesUsed := make(map[string]bool) coins := make([]*types.NotoCoin, len(states)) refs := make([]*pb.StateRef, len(states)) total := big.NewInt(0) - for i, input := range states { - if input.SchemaId != n.coinSchema.Id { - return nil, nil, nil, i18n.NewError(ctx, msgs.MsgUnknownSchema, input.SchemaId) + for i, state := range states { + if state.SchemaId != n.coinSchema.Id { + return nil, nil, nil, i18n.NewError(ctx, msgs.MsgUnknownSchema, state.SchemaId) + } + if statesUsed[state.Id] { + return nil, nil, nil, i18n.NewError(ctx, msgs.MsgDuplicateStateInList, label, i, state.Id) } - if coins[i], err = n.unmarshalCoin(input.StateDataJson); err != nil { - return nil, nil, nil, i18n.NewError(ctx, msgs.MsgInvalidListInput, label, i, input.Id, err) + statesUsed[state.Id] = true + if coins[i], err = n.unmarshalCoin(state.StateDataJson); err != nil { + return nil, nil, nil, i18n.NewError(ctx, msgs.MsgInvalidListInput, label, i, state.Id, err) } refs[i] = &pb.StateRef{ - SchemaId: input.SchemaId, - Id: input.Id, + SchemaId: state.SchemaId, + Id: state.Id, } total = total.Add(total, coins[i].Amount.BigInt()) } diff --git a/domains/noto/internal/noto/states.go b/domains/noto/internal/noto/states.go index 483d0c6b7..37bf37ec2 100644 --- a/domains/noto/internal/noto/states.go +++ b/domains/noto/internal/noto/states.go @@ -89,6 +89,7 @@ func (n *Noto) prepareInputs(ctx context.Context, owner ethtypes.Address0xHex, a stateRefs := []*pb.StateRef{} coins := []*types.NotoCoin{} for { + // TODO: make this configurable queryBuilder := query.NewQueryBuilder(). Limit(10). Sort(".created").