package keeper import ( "context" "encoding/json" "errors" "fmt" "math/big" "strconv" "colinear/x/colinearcore/memdb" "colinear/x/colinearcore/types" sdk "github.com/cosmos/cosmos-sdk/types" ) func (k msgServer) NewAuction(goCtx context.Context, msg *types.MsgNewAuction) (*types.MsgNewAuctionResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) next, found := k.Keeper.GetNextAuction(ctx) if !found { return nil, errors.New("unable to get next auction index") } index := strconv.FormatUint(next.AuctionId, 10) auctionLen := msg.LeaseEnd - uint64(ctx.BlockTime().Unix()) // check that submitted denom is allowed for _, denom := range k.GetParams(ctx).AllowedAuctionDenoms { if msg.Denom == denom { goto found } } return nil, fmt.Errorf("denom %s is not allowed; must be in %v", msg.Denom, k.GetParams(ctx).AllowedAuctionDenoms) found: if uint32(auctionLen) < k.GetParams(ctx).MinLeasePeriod { return nil, fmt.Errorf( "Auction length %d is below min lease period of %d", auctionLen, k.GetParams(ctx).MinLeasePeriod, ) } if uint32(auctionLen) > k.GetParams(ctx).MaxLeasePeriod { return nil, fmt.Errorf( "Auction length %d is above max lease period of %d", auctionLen, k.GetParams(ctx).MaxLeasePeriod, ) } senderAddr, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { return nil, fmt.Errorf("sender address `%s` format invalid (bech32 required)", msg.Creator) } if uint32(len(msg.VerifiedProviders)) > k.GetParams(ctx).MaxVerifiedProviders { return nil, fmt.Errorf("must submit no more than %d verified providers (got %d)", k.GetParams(ctx).MaxVerifiedProviders, len(msg.VerifiedProviders)) } bech32Len := len("colinear") + 39 for i, provider := range msg.VerifiedProviders { if len(provider) > bech32Len { return nil, fmt.Errorf("verified provider address %s (#%d) must be no longer than a colinear bech32 address (%d)", provider, i, bech32Len) } } if msg.VerifiedProviders != nil { memdb.AuctionDB.SetVerifiedProviders(index, msg.VerifiedProviders) } else { memdb.AuctionDB.SetVerifiedProviders(index, []string{}) } auction := types.Auction{ Index: index, Name: msg.Name, Description: msg.Description, // best bid -> null // Best: new(types.Bid), Deadline: uint64(ctx.BlockHeight()) + uint64(k.GetParams(ctx).AuctionTime), Denom: msg.Denom, Owner: msg.Creator, Ceiling: msg.Ceiling, // lease start -> null // instead, initialize when auction is finalized // LeaseStart: uint64(ctx.BlockTime().Unix()), LeaseEnd: msg.LeaseEnd, // remaining payout -> null // Remaining: "0", Gpus: msg.Gpus, CpuArch: msg.CpuArch, CpuCores: msg.CpuCores, MemMb: msg.MemMb, StorageGb: msg.StorageGb, } // validate hardware if err := k.ValidateAuctionHardware(ctx, auction); err != nil { return nil, err } spendable := k.bank.SpendableCoins(ctx, senderAddr) // if balance does not exceed or equal proposed auction ceiling... ceiling := new(big.Int) ceiling.SetString(auction.Ceiling, 10) if spendable.AmountOf(auction.Denom).BigInt().Cmp(ceiling) == -1 { return nil, fmt.Errorf("not enough balance to set ceiling %s%s", msg.Ceiling, auction.Denom) } coins := sdk.NewCoins(sdk.Coin{ Amount: sdk.NewIntFromBigInt(ceiling), Denom: auction.Denom, }) if err := k.Keeper.bank.SendCoinsFromAccountToModule(ctx, senderAddr, types.ModuleName, coins); err != nil { return nil, fmt.Errorf("failed to transfer %s%s", auction.Ceiling, auction.Denom) } k.Keeper.SetAuction(ctx, auction) next.AuctionId++ k.Keeper.SetNextAuction(ctx, types.NextAuction{AuctionId: next.AuctionId}) gpuList, err := json.Marshal(msg.Gpus) if err != nil { return nil, fmt.Errorf("failed to marshal gpu list %v", msg.Gpus) } ctx.EventManager().EmitEvent( sdk.NewEvent( types.AuctionCreatedEventType, sdk.NewAttribute(types.AuctionCreatedIndex, auction.Index), sdk.NewAttribute(types.AuctionCreatedEventCreator, msg.Creator), sdk.NewAttribute(types.AuctionCreatedCeiling, msg.Ceiling), sdk.NewAttribute(types.AuctionCreatedCpuCores, msg.CpuArch.String()), sdk.NewAttribute(types.AuctionCreatedCpuArch, msg.CpuArch.String()), sdk.NewAttribute(types.AuctionCreatedGpus, string(gpuList)), sdk.NewAttribute(types.AuctionCreatedMemMb, fmt.Sprint(msg.MemMb)), sdk.NewAttribute(types.AuctionCreatedStorage, fmt.Sprint(msg.StorageGb)), ), ) return &types.MsgNewAuctionResponse{ AuctionId: strconv.FormatUint(next.AuctionId, 10), }, nil } func (k *Keeper) ValidateAuctionHardware(ctx sdk.Context, a types.Auction) error { if a.Gpus == nil { return errors.New("GPU list not found") } // move these to module params later if len(a.Gpus) == 0 || len(a.Gpus) > 20 { return fmt.Errorf("must order between 1 and 20 GPUs (got %d)", len(a.Gpus)) } for _, gpu := range a.Gpus { if _, ok := k.GetParams(ctx).GpuModels[gpu]; !ok { return fmt.Errorf("GPU model %s invalid", gpu) } } // move these to module params later if a.CpuCores < 1 || a.CpuCores > 12 { return fmt.Errorf("CPU Cores must be between 1 and 12 (got %d)", a.CpuCores) } // move these to module params later if a.StorageGb < 25 || a.StorageGb > 2000 { return fmt.Errorf("storage must be between 25GB and 2TB (got %dGB)", a.StorageGb) } return nil }