Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions x/market/hooks/external.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ type DeploymentKeeper interface {
GetGroups(ctx sdk.Context, id dv1.DeploymentID) dtypes.Groups
CloseDeployment(ctx sdk.Context, deployment dv1.Deployment) error
OnCloseGroup(ctx sdk.Context, group dtypes.Group, state dtypes.Group_State) error
OnPauseGroup(ctx sdk.Context, group dtypes.Group) error
}

type MarketKeeper interface {
GetOrder(ctx sdk.Context, id mv1.OrderID) (mtypes.Order, bool)
GetBid(ctx sdk.Context, id mv1.BidID) (mtypes.Bid, bool)
GetLease(ctx sdk.Context, id mv1.LeaseID) (mv1.Lease, bool)
OnGroupClosed(ctx sdk.Context, id dv1.GroupID) error
OnGroupPaused(ctx sdk.Context, id dv1.GroupID) error
OnOrderClosed(ctx sdk.Context, order mtypes.Order) error
OnBidClosed(ctx sdk.Context, bid mtypes.Bid) error
OnLeaseClosed(ctx sdk.Context, lease mv1.Lease, state mv1.Lease_State, reason mv1.LeaseClosedReason) error
Expand Down
26 changes: 19 additions & 7 deletions x/market/hooks/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,29 @@ func (h *hooks) OnEscrowAccountClosed(ctx sdk.Context, obj etypes.Account) {
if deployment.State != dv1.DeploymentActive {
return
}
_ = h.dkeeper.CloseDeployment(ctx, deployment)

gstate := dtypes.GroupClosed
if obj.State.State == etypes.StateOverdrawn {
gstate = dtypes.GroupInsufficientFunds
var gstate dtypes.Group_State

switch obj.State.State {
case etypes.StateOverdrawn:
gstate = dtypes.GroupPaused
default:
gstate = dtypes.GroupClosed
_ = h.dkeeper.CloseDeployment(ctx, deployment)
}

for _, group := range h.dkeeper.GetGroups(ctx, deployment.ID) {
if group.ValidateClosable() == nil {
_ = h.dkeeper.OnCloseGroup(ctx, group, gstate)
_ = h.mkeeper.OnGroupClosed(ctx, group.ID)
switch gstate {
case dtypes.GroupPaused:
if group.ValidatePausable() == nil {
_ = h.dkeeper.OnPauseGroup(ctx, group)
_ = h.mkeeper.OnGroupPaused(ctx, group.ID)
}
case dtypes.GroupClosed:
if group.ValidateClosable() == nil {
_ = h.dkeeper.OnCloseGroup(ctx, group, gstate)
_ = h.mkeeper.OnGroupClosed(ctx, group.ID)
}
}
}
}
Expand Down
197 changes: 197 additions & 0 deletions x/market/hooks/hooks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package hooks_test

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

dv1 "pkg.akt.dev/go/node/deployment/v1"
dtypes "pkg.akt.dev/go/node/deployment/v1beta4"
ev1 "pkg.akt.dev/go/node/escrow/id/v1"
etypes "pkg.akt.dev/go/node/escrow/types/v1"
"pkg.akt.dev/go/testutil"
cmocks "pkg.akt.dev/node/testutil/cosmos/mocks"
"pkg.akt.dev/node/testutil/state"
dkeeper "pkg.akt.dev/node/x/deployment/keeper"
ekeeper "pkg.akt.dev/node/x/escrow/keeper"
"pkg.akt.dev/node/x/market/hooks"
mkeeper "pkg.akt.dev/node/x/market/keeper"
)

type testSuite struct {
t testing.TB
ctx sdk.Context
ekeeper ekeeper.Keeper
dkeeper dkeeper.IKeeper
mkeeper mkeeper.IKeeper
bkeeper *cmocks.BankKeeper
}

type testSeedData struct {
did dv1.DeploymentID
aid ev1.Account
}

type testInput struct {
accountState etypes.AccountState
deploymentState dv1.Deployment_State
groupState dtypes.Group_State
}

func setupTestSuite(t *testing.T) *testSuite {
ssuite := state.SetupTestSuite(t)

suite := &testSuite{
t: t,
ctx: ssuite.Context(),
ekeeper: ssuite.EscrowKeeper(),
dkeeper: ssuite.DeploymentKeeper(),
mkeeper: ssuite.MarketKeeper(),
bkeeper: ssuite.BankKeeper(),
}

return suite
}

func TestEscrowAccountClose(t *testing.T) {
suite := setupTestSuite(t)

tests := []struct {
description string
testInput testInput
expectedDeploymentState dv1.Deployment_State
expectedGroupState dtypes.Group_State
}{
{
"Overdrawn account when deployment is active",
testInput{
etypes.AccountState{
State: etypes.StateOverdrawn,
},
dv1.DeploymentActive,
dtypes.GroupOpen,
},
dv1.DeploymentActive,
dtypes.GroupPaused,
},
{
"Overdrawn account when deployment is closed",
testInput{
etypes.AccountState{
State: etypes.StateOverdrawn,
},
dv1.DeploymentClosed,
dtypes.GroupClosed,
},
dv1.DeploymentClosed,
dtypes.GroupClosed,
},
{
"Account in good standing when deployment is active",
testInput{
etypes.AccountState{
State: etypes.StateOpen,
},
dv1.DeploymentActive,
dtypes.GroupOpen,
},
dv1.DeploymentClosed,
dtypes.GroupClosed,
},
{
"Account in good standing when deployment is closed",
testInput{
etypes.AccountState{
State: etypes.StateOpen,
},
dv1.DeploymentClosed,
dtypes.GroupClosed,
},
dv1.DeploymentClosed,
dtypes.GroupClosed,
},
{
"Account already closed",
testInput{
etypes.AccountState{
State: etypes.StateClosed,
},
dv1.DeploymentActive,
dtypes.GroupOpen,
},
dv1.DeploymentClosed,
dtypes.GroupClosed,
},
{
"Account is in an invalid state",
testInput{
etypes.AccountState{
State: etypes.StateInvalid,
},
dv1.DeploymentActive,
dtypes.GroupOpen,
},
dv1.DeploymentClosed,
dtypes.GroupClosed,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
ctx := suite.ctx
hooks := hooks.New(suite.dkeeper, suite.mkeeper)

seedData := setupSeedData(t, suite, tt.testInput)

hooks.OnEscrowAccountClosed(
ctx,
etypes.Account{
ID: seedData.aid,
State: tt.testInput.accountState,
})

deployment, found := suite.dkeeper.GetDeployment(ctx, seedData.did)
assert.NotNil(t, deployment)
assert.True(t, found)

assert.Equal(t, tt.expectedDeploymentState, deployment.State)

groups := suite.dkeeper.GetGroups(ctx, seedData.did)

for _, g := range groups {
assert.Equal(t, tt.expectedGroupState, g.State)
}
})
}
}

func setupSeedData(t testing.TB, suite *testSuite, ti testInput) testSeedData {
t.Helper()

ctx := suite.ctx

did := testutil.DeploymentID(t)
aid := did.ToEscrowAccountID()

deployment := dv1.Deployment{
ID: did,
State: ti.deploymentState,
}

groupCount := 3

groups := make([]dtypes.Group, groupCount)
for i := range groups {
groups[i] = testutil.DeploymentGroup(t, did, uint32(i)+1)
groups[i].State = ti.groupState
}

require.NoError(t, suite.dkeeper.Create(ctx, deployment, groups))

return testSeedData{
did: did,
aid: aid,
}
}
21 changes: 18 additions & 3 deletions x/market/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type IKeeper interface {
OnOrderClosed(ctx sdk.Context, order types.Order) error
OnLeaseClosed(ctx sdk.Context, lease mv1.Lease, state mv1.Lease_State, reason mv1.LeaseClosedReason) error
OnGroupClosed(ctx sdk.Context, id dtypes.GroupID) error
OnGroupPaused(ctx sdk.Context, id dtypes.GroupID) error
GetOrder(ctx sdk.Context, id mv1.OrderID) (types.Order, bool)
GetBid(ctx sdk.Context, id mv1.BidID) (types.Bid, bool)
GetLease(ctx sdk.Context, id mv1.LeaseID) (mv1.Lease, bool)
Expand Down Expand Up @@ -346,17 +347,31 @@ func (k Keeper) OnLeaseClosed(ctx sdk.Context, lease mv1.Lease, state mv1.Lease_
return nil
}

// OnGroupClosed updates state of all orders, bids and leases in group to closed
// OnGroupClosed updates market resources when the group is closed
func (k Keeper) OnGroupClosed(ctx sdk.Context, id dtypes.GroupID) error {
// OnGroupClosed is callable by x/deployment only so only reason is owner
return k.closeMarketResourcesForGroup(ctx, id, mv1.LeaseClosedReasonOwner)
}

// OnGroupPaused updates market resources when the group is paused
func (k Keeper) OnGroupPaused(ctx sdk.Context, id dtypes.GroupID) error {
// OnGroupPaused can only be called when the group is paused due to insufficient funds (at the moment).
// This is hardcoded here to avoid passing extra parameters through multiple layers.
// And avoid exposing the lease closed reason in the signature.
// TODO Consider adding a group paused reason in the future if needed.
return k.closeMarketResourcesForGroup(ctx, id, mv1.LeaseClosedReasonInsufficientFunds)
}

// closeMarketResourcesForGroup updates the state of all orders, bids and leases to closed for the associated group
func (k Keeper) closeMarketResourcesForGroup(ctx sdk.Context, id dtypes.GroupID, reason mv1.LeaseClosedReason) error {
processClose := func(ctx sdk.Context, bid types.Bid) error {
err := k.OnBidClosed(ctx, bid)
if err != nil {
return err
}

if lease, ok := k.GetLease(ctx, bid.ID.LeaseID()); ok {
// OnGroupClosed is callable by x/deployment only so only reason is owner
err = k.OnLeaseClosed(ctx, lease, mv1.LeaseClosed, mv1.LeaseClosedReasonOwner)
err = k.OnLeaseClosed(ctx, lease, mv1.LeaseClosed, reason)
if err := k.ekeeper.PaymentClose(ctx, lease.ID.ToEscrowPaymentID()); err != nil {
ctx.Logger().With("err", err).Info("error closing payment")
}
Expand Down