Skip to content

Commit

Permalink
fix: lien accounts must proxy all account methods
Browse files Browse the repository at this point in the history
  • Loading branch information
JimLarson authored and mergify[bot] committed Oct 13, 2021
1 parent f4a0ba1 commit db79c42
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 25 deletions.
94 changes: 77 additions & 17 deletions golang/cosmos/x/lien/keeper/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (

"github.com/Agoric/agoric-sdk/golang/cosmos/x/lien/types"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
"github.com/gogo/protobuf/proto"
)

// maxCoins returns coins with the maximum amount of each denomination
Expand Down Expand Up @@ -49,28 +51,69 @@ func maxCoins(a, b sdk.Coins) sdk.Coins {
return sdk.NewCoins(max...)
}

// LienAccount wraps a VestingAccount to implement lien encumbrance.
// omniAccount is the full expected interface of non-module accounts.
// In addition to the methods declared in authtypes.AccountI, additional
// expectations are enforced dynamically through casting and reflection:
//
// - non-module accounts are expected to obey the GenesisAccount interface,
// i.e. to have a Validate() method;
//
// - UnpackInterfacesMessage is needed for unpacking accounts embedded
// in an Any message;
//
// - MarshalYAML() is used for String rendering;
//
// - protubuf Messages are expected to implement a number of "XXX"-prefixed
// methods not visible in the Message interface.
//
// Declaring the expected methods here allows them to implicitly fall trhough
// to an embedded omniAccount.
//
// Note that this interface will have to adapt to updated expectations in
// the cosmos-sdk or protobuf library.
type omniAccount interface {
authtypes.GenesisAccount
codectypes.UnpackInterfacesMessage
MarshalYAML() (interface{}, error)
XXX_DiscardUnknown()
XXX_Marshal([]byte, bool) ([]byte, error)
XXX_Merge(proto.Message)
XXX_Size() int
XXX_Unmarshal([]byte) error
}

// omniVestingAccount is an omniAccount plus vesting methods.
type omniVestingAccount interface {
omniAccount
vestexported.VestingAccount
}

// LienAccount wraps an omniVestingAccount to implement lien encumbrance.
// The LockedCoins() method is the maximum of the coins locked for
// liens, and the coins locked in the underlying VestingAccount.
// It inherits the marshaling behavior of the wrapped account.
// In particular, the Lien account must be passed by pointer because of
// expectations from the proto library.
type LienAccount struct {
vestexported.VestingAccount
omniVestingAccount
lienKeeper Keeper
// messageName string
}

var _ vestexported.VestingAccount = LienAccount{}
var _ vestexported.VestingAccount = &LienAccount{}
var _ authtypes.GenesisAccount = &LienAccount{}

// LockedCoins implements the method from the VestingAccount interface.
// It takes the maximum of the coins locked for liens and the coins
// locked in the wrapped VestingAccount.
func (la LienAccount) LockedCoins(ctx sdk.Context) sdk.Coins {
wrappedLocked := la.VestingAccount.LockedCoins(ctx)
func (la *LienAccount) LockedCoins(ctx sdk.Context) sdk.Coins {
wrappedLocked := la.omniVestingAccount.LockedCoins(ctx)
lienedLocked := la.LienedLockedCoins(ctx)
return maxCoins(wrappedLocked, lienedLocked)
}

// Returns the coins which are locked for lien encumbrance.
func (la LienAccount) LienedLockedCoins(ctx sdk.Context) sdk.Coins {
func (la *LienAccount) LienedLockedCoins(ctx sdk.Context) sdk.Coins {
state := la.lienKeeper.GetAccountState(ctx, la.GetAddress())
return computeLienLocked(state.Liened, state.Bonded, state.Unbonding)
}
Expand All @@ -86,6 +129,14 @@ func computeLienLocked(liened, bonded, unbonding sdk.Coins) sdk.Coins {
return maxCoins(subtrahend, liened).Sub(subtrahend)
}

// XXX_MessageName provides the message name for JSON serialization.
// See proto.MessageName().
func (la *LienAccount) XXX_MessageName() string {
// Find the embedded account's message name for JSON
// serialization and record it for impersonation.
return proto.MessageName(la.omniVestingAccount)
}

// NewAccountWrapper returns an AccountWrapper which wraps any account
// to be a LienAccount associated with the given Keeper, then unwraps
// any layers that the Wrap added.
Expand All @@ -95,17 +146,21 @@ func NewAccountWrapper(lk Keeper) types.AccountWrapper {
if acc == nil {
return nil
}
return LienAccount{
VestingAccount: makeVesting(acc),
lienKeeper: lk,
if omni, ok := acc.(omniAccount); ok {
return &LienAccount{
omniVestingAccount: makeVesting(omni),
lienKeeper: lk,
}
}
// don't wrap non-omni accounts, e.g. module accounts
return acc
},
Unwrap: func(acc authtypes.AccountI) authtypes.AccountI {
if la, ok := acc.(LienAccount); ok {
acc = la.VestingAccount
if la, ok := acc.(*LienAccount); ok {
acc = la.omniVestingAccount
}
if uva, ok := acc.(unlockedVestingAccount); ok {
acc = uva.AccountI
acc = uva.omniAccount
}
return acc
},
Expand All @@ -114,21 +169,22 @@ func NewAccountWrapper(lk Keeper) types.AccountWrapper {

// makeVesting returns a VestingAccount, wrapping a non-vesting argument
// in a trivial implementation if necessary.
func makeVesting(acc authtypes.AccountI) vestexported.VestingAccount {
if v, ok := acc.(vestexported.VestingAccount); ok {
func makeVesting(acc omniAccount) omniVestingAccount {
if v, ok := acc.(omniVestingAccount); ok {
return v
}
return unlockedVestingAccount{AccountI: acc}
return unlockedVestingAccount{omniAccount: acc}
}

// unlockedVestingAccount extends an ordinary account to be a vesting account
// unlockedVestingAccount extends an omniAccount to be a vesting account
// by simulating an original vesting amount of zero. It inherets the marshal
// behavior of the wrapped account.
type unlockedVestingAccount struct {
authtypes.AccountI
omniAccount
}

var _ vestexported.VestingAccount = unlockedVestingAccount{}
var _ authtypes.GenesisAccount = unlockedVestingAccount{}

func (uva unlockedVestingAccount) LockedCoins(ctx sdk.Context) sdk.Coins {
return sdk.NewCoins()
Expand Down Expand Up @@ -167,3 +223,7 @@ func (uva unlockedVestingAccount) GetDelegatedFree() sdk.Coins {
func (uva unlockedVestingAccount) GetDelegatedVesting() sdk.Coins {
return sdk.NewCoins()
}

func (uva unlockedVestingAccount) XXX_MessageName() string {
return proto.MessageName(uva.omniAccount)
}
2 changes: 1 addition & 1 deletion golang/cosmos/x/lien/keeper/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func TestWrap(t *testing.T) {
keeper := keeperImpl{accountKeeper: &wak}
wrapper := NewAccountWrapper(keeper)
wrapped := wrapper.Wrap(acc)
lienAcc, ok := wrapped.(LienAccount)
lienAcc, ok := wrapped.(*LienAccount)
if !ok {
t.Fatalf("wrapper did not create a lien account: %+v", wrapped)
}
Expand Down
12 changes: 5 additions & 7 deletions golang/cosmos/x/lien/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,11 @@ func (lk keeperImpl) getLocked(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
if account == nil {
return sdk.NewCoins()
}
lienAccount, ok := account.(LienAccount)
if ok {
account = lienAccount.VestingAccount
if lienAccount, ok := account.(LienAccount); ok {
return lienAccount.omniVestingAccount.GetVestingCoins(ctx.BlockTime())
}
vestingAccount, ok := account.(vestexported.VestingAccount)
if !ok {
return sdk.NewCoins()
if vestingAccount, ok := account.(vestexported.VestingAccount); ok {
return vestingAccount.GetVestingCoins(ctx.BlockTime())
}
return vestingAccount.GetVestingCoins(ctx.BlockTime())
return sdk.NewCoins()
}
9 changes: 9 additions & 0 deletions packages/cosmic-swingset/test/test-make.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,13 @@ test('make and exec', async t => {
resolve();
}),
);
await new Promise(resolve =>
spawn('bin/ag-chain-cosmos', ['export', '--home=t1/n0'], {
cwd: `${dirname}/..`,
stdio: ['ignore', 'ignore', 'inherit'],
}).addListener('exit', code => {
t.is(code, 0, 'export exits successfully');
resolve();
}),
);
});

0 comments on commit db79c42

Please sign in to comment.