Keeper

The main logic of the House module definitions and methods.

Create Subaccount

There are several steps for subaccount create to be made:

  1. Check for existing subaccount.

  2. Generate a new ID.

  3. Create a new account extracted from the generated ID.

  4. Set the new account into the account keeper.

  5. Store the owner, locked balances, and account summary in the state.

func (k Keeper) CreateSubAccount(ctx sdk.Context, creator, owner string,
	lockedBalances []types.LockedBalance,
) (string, error) {
	lockedBalance, err := sumlockedBalance(ctx, lockedBalances)
	if err != nil {
		return "", err
	}

	creatorAddr := sdk.MustAccAddressFromBech32(creator)
	subAccOwnerAddr := sdk.MustAccAddressFromBech32(owner)
	if _, exists := k.GetSubAccountByOwner(ctx, subAccOwnerAddr); exists {
		return "", types.ErrSubaccountAlreadyExist
	}

	subaccountID := k.NextID(ctx)

	// ALERT: If someone frontruns the account creation, will be overwritten here
	subAccAddr := types.NewAddressFromSubaccount(subaccountID)
	subaccountAccount := k.accountKeeper.NewAccountWithAddress(ctx, subAccAddr)
	k.accountKeeper.SetAccount(ctx, subaccountAccount)

	err = k.sendCoinsToSubaccount(ctx, creatorAddr, subAccAddr, lockedBalance)
	if err != nil {
		return "", sdkerrors.Wrap(err, "unable to send coins")
	}

	k.SetSubAccountOwner(ctx, subAccAddr, subAccOwnerAddr)
	k.SetLockedBalances(ctx, subAccAddr, lockedBalances)
	k.SetAccountSummary(ctx, subAccAddr, types.AccountSummary{
		DepositedAmount: lockedBalance,
		SpentAmount:     sdk.ZeroInt(),
		WithdrawnAmount: sdk.ZeroInt(),
		LostAmount:      sdk.ZeroInt(),
	})
	return subAccAddr.String(), nil
}

TopUp

There are several steps for a subaccount top-up to be made:

  1. Get account summary and locked balances from the state.

  2. Update the account Summary and locked balances.

  3. Send tokens from the transaction signer to the subaccount balance in the bank module.

// TopUp tops up the subaccount balance.
func (k Keeper) TopUp(ctx sdk.Context, creator, subAccOwnerAddr string,
	lockedBalance []types.LockedBalance,
) (string, error) {
	addedBalance, err := sumlockedBalance(ctx, lockedBalance)
	if err != nil {
		return "", err
	}

	creatorAddr := sdk.MustAccAddressFromBech32(creator)
	subaccountOwner := sdk.MustAccAddressFromBech32(subAccOwnerAddr)

	subAccAddr, exists := k.GetSubAccountByOwner(ctx, subaccountOwner)
	if !exists {
		return "", types.ErrSubaccountDoesNotExist
	}
	balance, exists := k.GetAccountSummary(ctx, subAccAddr)
	if !exists {
		panic("data corruption: subaccount exists but balance does not")
	}

	balance.DepositedAmount = balance.DepositedAmount.Add(addedBalance)
	k.SetAccountSummary(ctx, subAccAddr, balance)
	k.SetLockedBalances(ctx, subAccAddr, lockedBalance)

	err = k.sendCoinsToSubaccount(ctx, creatorAddr, subAccAddr, addedBalance)
	if err != nil {
		return "", sdkerrors.Wrapf(types.ErrSendCoinError, "%s", err)
	}
	return subAccAddr.String(), nil
}

Withdraw

There are several steps for a subaccount withdrawal to be made:

  1. Get the account summary from the state.

  2. Calculate the withdrawable balance according to the unlock timestamps and spend and lost amounts.

  3. Update the account summary withdrawn amount.

  4. Send tokens from the subaccount to the owner's balance.

// withdrawUnlocked returns the balance, unlocked balance and bank balance of a subaccount
func (k Keeper) withdrawUnlocked(ctx sdk.Context, subAccAddr sdk.AccAddress, ownerAddr sdk.AccAddress) error {
	accSummary, unlockedBalance, bankBalance := k.getAccountSummary(ctx, subAccAddr)

	withdrawableBalance := accSummary.WithdrawableBalance(unlockedBalance, bankBalance.Amount)
	if withdrawableBalance.IsZero() {
		return types.ErrNothingToWithdraw
	}

	if err := accSummary.Withdraw(withdrawableBalance); err != nil {
		return err
	}

	k.SetAccountSummary(ctx, subAccAddr, accSummary)

	err := k.bankKeeper.SendCoins(ctx, subAccAddr, ownerAddr, sdk.NewCoins(sdk.NewCoin(params.DefaultBondDenom, withdrawableBalance)))
	if err != nil {
		return err
	}

	return nil
}

Wager

There are several steps for a wager by a subaccount to be made:

  1. Check the existence of the subaccount for the bettor.

  2. Verify the ticket and unmarshal the payload and validate.

  3. Use bet module's methods to prepare a bet object.

  4. Withdraw the needed amount first from the unlocked, then the locked if there is not enough unlocked balance.

  5. Use bet module to place the bet for the owner's account.

func (k msgServer) Wager(goCtx context.Context, msg *types.MsgWager) (*types.MsgWagerResponse, error) {
	ctx := sdk.UnwrapSDKContext(goCtx)

	if !k.keeper.GetWagerEnabled(ctx) {
		return nil, sdkerrors.Wrapf(sdkerrtypes.ErrInvalidRequest, "currently the subacount wager tx is not enabled")
	}

	subAccOwner := sdk.MustAccAddressFromBech32(msg.Creator)
	// find subaccount
	subAccAddr, exists := k.keeper.GetSubAccountByOwner(ctx, subAccOwner)
	if !exists {
		return nil, status.Error(codes.NotFound, "subaccount not found")
	}

	payload := &types.SubAccWagerTicketPayload{}
	err := k.keeper.ovmKeeper.VerifyTicketUnmarshal(sdk.WrapSDKContext(ctx), msg.Ticket, &payload)
	if err != nil {
		return nil, sdkerrors.Wrapf(types.ErrInTicketVerification, "%s", err)
	}

	if msg.Creator != payload.Msg.Creator {
		return nil, sdkerrors.Wrapf(sdkerrtypes.ErrInvalidRequest, "message creator should be the same as the sub message creator%s", msg.Creator)
	}

	bet, oddsMap, err := k.keeper.betKeeper.PrepareBetObject(ctx, payload.Msg.Creator, payload.Msg.Props)
	if err != nil {
		return nil, err
	}

	if err := payload.Validate(bet.Amount); err != nil {
		return nil, sdkerrors.Wrapf(types.ErrInTicketPayloadValidation, "%s", err)
	}

	mainAccBalance := k.keeper.bankKeeper.GetBalance(
		ctx,
		sdk.MustAccAddressFromBech32(bet.Creator),
		params.DefaultBondDenom)
	if mainAccBalance.Amount.LT(payload.MainaccDeductAmount) {
		return nil, sdkerrors.Wrapf(sdkerrtypes.ErrInvalidRequest, "not enough balance in main account")
	}

	if err := k.keeper.withdrawLockedAndUnlocked(ctx, subAccAddr, subAccOwner, payload.SubaccDeductAmount); err != nil {
		return nil, err
	}

	if err := k.keeper.betKeeper.Wager(ctx, bet, oddsMap); err != nil {
		return nil, err
	}

	msg.EmitEvent(&ctx, payload.Msg, subAccOwner.String())

	return &types.MsgWagerResponse{
		Response: &bettypes.MsgWagerResponse{Props: payload.Msg.Props},
	}, nil
}

Last updated