package paychan import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "testing" ) func TestKeeper(t *testing.T) { t.Run("CreateChannel", func(t *testing.T) { // TODO test for receiver account not existing (OK) and sender not existing (not ok) accountSeeds := []string{"senderSeed", "receiverSeed"} const ( senderAccountIndex int = 0 receiverAccountIndex int = 1 ) _, addrs, _, _ := createTestGenAccounts(accountSeeds, sdk.Coins{}) // pure function testCases := []struct { name string sender sdk.AccAddress receiver sdk.AccAddress coins sdk.Coins shouldCreateChannel bool shouldError bool }{ { "HappyPath", addrs[senderAccountIndex], addrs[receiverAccountIndex], sdk.Coins{sdk.NewCoin("KVA", 10)}, true, false, }, { "NilAddress", sdk.AccAddress{}, sdk.AccAddress{}, sdk.Coins{sdk.NewCoin("KVA", 10)}, false, true, }, { "NilCoins", addrs[senderAccountIndex], addrs[receiverAccountIndex], sdk.Coins{}, false, true, }, { "NegativeCoins", addrs[senderAccountIndex], addrs[receiverAccountIndex], sdk.Coins{sdk.NewCoin("KVA", -57)}, false, true, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { ////// SETUP // create basic mock app ctx, coinKeeper, channelKeeper, addrs, _, _, genAccFunding := createMockApp(accountSeeds) // ////// ACTION _, err := channelKeeper.CreateChannel(ctx, testCase.sender, testCase.receiver, testCase.coins) // ////// CHECK RESULTS // Check error if testCase.shouldError { assert.Error(t, err) } else { assert.NoError(t, err) } // Check if channel exists and is correct channelID := ChannelID(0) // should be 0 as first channel createdChan, found := channelKeeper.getChannel(ctx, channelID) if testCase.shouldCreateChannel { expectedChan := Channel{ ID: channelID, Participants: [2]sdk.AccAddress{testCase.sender, testCase.receiver}, Coins: testCase.coins, } // channel exists and correct assert.True(t, found) assert.Equal(t, expectedChan, createdChan) // check coins deducted from sender assert.Equal(t, genAccFunding.Minus(testCase.coins), coinKeeper.GetCoins(ctx, testCase.sender)) // check no coins deducted from receiver assert.Equal(t, genAccFunding, coinKeeper.GetCoins(ctx, testCase.receiver)) // check next global channelID incremented assert.Equal(t, ChannelID(1), channelKeeper.getNewChannelID(ctx)) } else { // channel doesn't exist assert.False(t, found) assert.Equal(t, Channel{}, createdChan) // check no coins deducted from sender assert.Equal(t, genAccFunding, coinKeeper.GetCoins(ctx, addrs[senderAccountIndex])) // check no coins deducted from receiver assert.Equal(t, genAccFunding, coinKeeper.GetCoins(ctx, addrs[receiverAccountIndex])) // check next global channelID not incremented assert.Equal(t, ChannelID(0), channelKeeper.getNewChannelID(ctx)) } }) } }) t.Run("CloseChannelByReceiver", func(t *testing.T) { // TODO convert to table driven and add more test cases // channel exists or not (assume channels correct) // various Updates // submittedUpdates existing or not (assume they are valid) // SETUP accountSeeds := []string{"senderSeed", "receiverSeed"} const ( senderAccountIndex int = 0 receiverAccountIndex int = 1 ) ctx, coinKeeper, channelKeeper, addrs, pubKeys, privKeys, genAccFunding := createMockApp(accountSeeds) coins := sdk.Coins{sdk.NewCoin("KVA", 10)} // create new channel channelID := ChannelID(0) // should be 0 as first channel channel := Channel{ ID: channelID, Participants: [2]sdk.AccAddress{addrs[senderAccountIndex], addrs[receiverAccountIndex]}, Coins: coins, } channelKeeper.setChannel(ctx, channel) // create closing update payout := Payout{sdk.Coins{sdk.NewCoin("KVA", 3)}, sdk.Coins{sdk.NewCoin("KVA", 7)}} update := Update{ ChannelID: channelID, Payout: payout, // empty sig } cryptoSig, _ := privKeys[senderAccountIndex].Sign(update.GetSignBytes()) update.Sigs = [1]UpdateSignature{UpdateSignature{ PubKey: pubKeys[senderAccountIndex], CryptoSignature: cryptoSig, }} // Set empty submittedUpdatesQueue TODO work out proper genesis initialisation channelKeeper.setSubmittedUpdatesQueue(ctx, SubmittedUpdatesQueue{}) // ACTION _, err := channelKeeper.CloseChannelByReceiver(ctx, update) // CHECK RESULTS // no error assert.NoError(t, err) // coins paid out senderPayout := payout[senderAccountIndex] assert.Equal(t, genAccFunding.Plus(senderPayout), coinKeeper.GetCoins(ctx, addrs[senderAccountIndex])) receiverPayout := payout[receiverAccountIndex] assert.Equal(t, genAccFunding.Plus(receiverPayout), coinKeeper.GetCoins(ctx, addrs[receiverAccountIndex])) // channel deleted _, found := channelKeeper.getChannel(ctx, channelID) assert.False(t, found) }) t.Run("InitCloseChannelBySender", func(t *testing.T) { // TODO do some documentation here // Ideally this should mock calls to ctx.store.Get/Set - test the side effects without being dependent on implementatino details // TODO test correct behaviour when a submittedUpdate already exists accountSeeds := []string{"senderSeed", "receiverSeed", "notInChannelSeed"} const ( senderAccountIndex int = 0 receiverAccountIndex int = 1 otherAccountIndex int = 2 ) chanID := ChannelID(0) type testUpdate struct { // A parameterised version of an Update for use in specifying test cases. channelID ChannelID // channelID of submitted update payout Payout // payout of submitted update pubKeyAccountIndex int // pubkey of signature of submitted update sigAccountIndex int // crypto signature of signature of submitted update } testCases := []struct { name string setupChannel bool updateToSubmit testUpdate expectedSubmittedUpdate string // "empty" or "sameAsSubmitted" shouldError bool }{ { "HappyPath", true, testUpdate{chanID, Payout{sdk.Coins{sdk.NewCoin("KVA", 3)}, sdk.Coins{sdk.NewCoin("KVA", 7)}}, senderAccountIndex, senderAccountIndex}, "sameAsSubmited", false, }, { "NoChannel", false, testUpdate{chanID, Payout{sdk.Coins{sdk.NewCoin("KVA", 3)}, sdk.Coins{sdk.NewCoin("KVA", 7)}}, senderAccountIndex, senderAccountIndex}, "empty", true, }, { "NoCoins", true, testUpdate{chanID, Payout{sdk.Coins{}}, senderAccountIndex, senderAccountIndex}, "empty", true, }, { "NegativeCoins", true, testUpdate{chanID, Payout{sdk.Coins{sdk.NewCoin("KVA", -5)}, sdk.Coins{sdk.NewCoin("KVA", 15)}}, senderAccountIndex, senderAccountIndex}, "empty", true, }, { "TooManyCoins", true, testUpdate{chanID, Payout{sdk.Coins{sdk.NewCoin("KVA", 100)}, sdk.Coins{sdk.NewCoin("KVA", 7)}}, senderAccountIndex, senderAccountIndex}, "empty", true, }, { "WrongSignature", true, testUpdate{chanID, Payout{sdk.Coins{sdk.NewCoin("KVA", 3)}, sdk.Coins{sdk.NewCoin("KVA", 7)}}, senderAccountIndex, otherAccountIndex}, "empty", true, }, { "WrongPubKey", true, testUpdate{chanID, Payout{sdk.Coins{sdk.NewCoin("KVA", 3)}, sdk.Coins{sdk.NewCoin("KVA", 7)}}, otherAccountIndex, senderAccountIndex}, "empty", true, }, { "ReceiverSigned", true, testUpdate{chanID, Payout{sdk.Coins{sdk.NewCoin("KVA", 3)}, sdk.Coins{sdk.NewCoin("KVA", 7)}}, receiverAccountIndex, receiverAccountIndex}, "empty", true, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { // SETUP ctx, _, channelKeeper, addrs, pubKeys, privKeys, _ := createMockApp(accountSeeds) // Set empty submittedUpdatesQueue TODO work out proper genesis initialisation channelKeeper.setSubmittedUpdatesQueue(ctx, SubmittedUpdatesQueue{}) // create new channel if testCase.setupChannel { channel := Channel{ ID: chanID, // should be 0 as first channel Participants: [2]sdk.AccAddress{addrs[senderAccountIndex], addrs[receiverAccountIndex]}, Coins: sdk.Coins{sdk.NewCoin("KVA", 10)}, } channelKeeper.setChannel(ctx, channel) } // create update // basic values updateToSubmit := Update{ ChannelID: testCase.updateToSubmit.channelID, Payout: testCase.updateToSubmit.payout, // empty sig } // create update's signature cryptoSig, _ := privKeys[testCase.updateToSubmit.sigAccountIndex].Sign(updateToSubmit.GetSignBytes()) updateToSubmit.Sigs = [1]UpdateSignature{UpdateSignature{ PubKey: pubKeys[testCase.updateToSubmit.pubKeyAccountIndex], CryptoSignature: cryptoSig, }} // ACTION _, err := channelKeeper.InitCloseChannelBySender(ctx, updateToSubmit) // CHECK RESULTS // Check error if testCase.shouldError { assert.Error(t, err) } else { assert.NoError(t, err) } // Check submittedUpdate su, found := channelKeeper.getSubmittedUpdate(ctx, chanID) switch testCase.expectedSubmittedUpdate { case "empty": assert.False(t, found) assert.Zero(t, su) case "sameAsSubmitted": assert.True(t, found) expectedSU := SubmittedUpdate{updateToSubmit, ChannelDisputeTime} assert.Equal(t, expectedSU, su) } }) } }) }