github.com/lino-network/lino@v0.6.11/x/developer/manager/manager.go (about)

     1  package developer
     2  
     3  import (
     4  	"fmt"
     5  
     6  	codec "github.com/cosmos/cosmos-sdk/codec"
     7  	sdk "github.com/cosmos/cosmos-sdk/types"
     8  
     9  	"github.com/lino-network/lino/param"
    10  	linotypes "github.com/lino-network/lino/types"
    11  	"github.com/lino-network/lino/utils"
    12  	"github.com/lino-network/lino/x/account"
    13  	"github.com/lino-network/lino/x/developer/model"
    14  	"github.com/lino-network/lino/x/developer/types"
    15  	"github.com/lino-network/lino/x/price"
    16  	"github.com/lino-network/lino/x/vote"
    17  	votetypes "github.com/lino-network/lino/x/vote/types"
    18  )
    19  
    20  const (
    21  	maxAffiliatedAccount = 500
    22  
    23  	exportVersion = 1
    24  	importVersion = 1
    25  )
    26  
    27  type DeveloperManager struct {
    28  	storage model.DeveloperStorage
    29  	// deps
    30  	paramHolder param.ParamKeeper
    31  	vote        vote.VoteKeeper
    32  	acc         account.AccountKeeper
    33  	price       price.PriceKeeper
    34  }
    35  
    36  // NewDeveloperManager - create new developer manager
    37  func NewDeveloperManager(key sdk.StoreKey, holder param.ParamKeeper, vote vote.VoteKeeper, acc account.AccountKeeper, price price.PriceKeeper) DeveloperManager {
    38  	return DeveloperManager{
    39  		storage:     model.NewDeveloperStorage(key),
    40  		paramHolder: holder,
    41  		vote:        vote,
    42  		acc:         acc,
    43  		price:       price,
    44  	}
    45  }
    46  
    47  // InitGenesis - init developer manager
    48  func (dm DeveloperManager) InitGenesis(ctx sdk.Context, reservePoolAmount linotypes.Coin) sdk.Error {
    49  	if !reservePoolAmount.IsNotNegative() {
    50  		return types.ErrInvalidReserveAmount(reservePoolAmount)
    51  	}
    52  	dm.storage.SetReservePool(ctx, &model.ReservePool{
    53  		Total: reservePoolAmount,
    54  	})
    55  	return nil
    56  }
    57  
    58  // DoesDeveloperExist - check if given developer exists and not deleted before.
    59  func (dm DeveloperManager) DoesDeveloperExist(ctx sdk.Context, username linotypes.AccountKey) bool {
    60  	dev, err := dm.storage.GetDeveloper(ctx, username)
    61  	if err != nil {
    62  		return false
    63  	}
    64  	return !dev.IsDeleted
    65  }
    66  
    67  func (dm DeveloperManager) GetDeveloper(ctx sdk.Context, username linotypes.AccountKey) (model.Developer, sdk.Error) {
    68  	dev, err := dm.storage.GetDeveloper(ctx, username)
    69  	if err != nil {
    70  		return model.Developer{}, err
    71  	}
    72  	return *dev, nil
    73  }
    74  
    75  // GetLiveDevelopers - returns all developers that are live(not deregistered).
    76  func (dm DeveloperManager) GetLiveDevelopers(ctx sdk.Context) []model.Developer {
    77  	rst := make([]model.Developer, 0)
    78  	devs := dm.storage.GetAllDevelopers(ctx)
    79  	for _, dev := range devs {
    80  		if !dev.IsDeleted {
    81  			rst = append(rst, dev)
    82  		}
    83  	}
    84  	return rst
    85  }
    86  
    87  // RegisterDeveloper - register a developer.
    88  // Stateful validation:
    89  // 1. account exists.
    90  // 2. has never been a developer.
    91  // 3. VoteDuty is simply a voter, not a validator candidate.
    92  // 4. not an affiliated account.
    93  // 4. has minimum LS.
    94  func (dm DeveloperManager) RegisterDeveloper(ctx sdk.Context, username linotypes.AccountKey, website, description, appMetaData string) sdk.Error {
    95  	if !dm.acc.DoesAccountExist(ctx, username) {
    96  		return types.ErrAccountNotFound()
    97  	}
    98  	if dm.storage.HasDeveloper(ctx, username) {
    99  		return types.ErrDeveloperAlreadyExist(username)
   100  	}
   101  	if duty, err := dm.vote.GetVoterDuty(ctx, username); err != nil || duty != votetypes.DutyVoter {
   102  		return types.ErrInvalidVoterDuty()
   103  	}
   104  	if dm.storage.HasUserRole(ctx, username) {
   105  		return types.ErrInvalidUserRole()
   106  	}
   107  	// check developer minimum LS requirement
   108  	param, err := dm.paramHolder.GetDeveloperParam(ctx)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	staked, err := dm.vote.GetLinoStake(ctx, username)
   113  	if err != nil {
   114  		return err
   115  	}
   116  	if !staked.IsGTE(param.DeveloperMinDeposit) {
   117  		return types.ErrInsufficientDeveloperDeposit()
   118  	}
   119  
   120  	// assign duty in vote
   121  	err = dm.vote.AssignDuty(ctx, username, votetypes.DutyApp, param.DeveloperMinDeposit)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	developer := &model.Developer{
   126  		Username:    username,
   127  		Website:     website,
   128  		Description: description,
   129  		AppMetaData: appMetaData,
   130  		IsDeleted:   false,
   131  	}
   132  	dm.storage.SetDeveloper(ctx, *developer)
   133  	return nil
   134  }
   135  
   136  // UpdateDeveloper - update developer.
   137  // 1. developer must not be deleted.
   138  func (dm DeveloperManager) UpdateDeveloper(ctx sdk.Context, username linotypes.AccountKey, website, description, appMetadata string) sdk.Error {
   139  	// Use DoesDeveloperExist to make sure we don't forget to check IsDeleted.
   140  	if !dm.DoesDeveloperExist(ctx, username) {
   141  		return types.ErrDeveloperNotFound()
   142  	}
   143  	developer, err := dm.GetDeveloper(ctx, username)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	developer.Website = website
   148  	developer.Description = description
   149  	developer.AppMetaData = appMetadata
   150  	dm.storage.SetDeveloper(ctx, developer)
   151  	return nil
   152  }
   153  
   154  // UnregisterDeveloper - unregister a developer
   155  // validation:
   156  // 1. Developer exists.
   157  // 2. No IDA issued or IDA revoked.
   158  // TODO:
   159  // remove all affiliated accounts.
   160  // mark developer as deleted.
   161  func (dm DeveloperManager) UnregisterDeveloper(ctx sdk.Context, username linotypes.AccountKey) sdk.Error {
   162  	return linotypes.ErrUnimplemented("UnregisterDeveloper")
   163  }
   164  
   165  // IssueIDA - Application issue IDA
   166  func (dm DeveloperManager) IssueIDA(ctx sdk.Context, appname linotypes.AccountKey, idaName string, idaPrice int64) sdk.Error {
   167  	if !dm.DoesDeveloperExist(ctx, appname) {
   168  		return types.ErrDeveloperNotFound()
   169  	}
   170  	// not issued before
   171  	if dm.storage.HasIDA(ctx, appname) {
   172  		return types.ErrIDAIssuedBefore()
   173  	}
   174  
   175  	// internally we store MiniIDAPrice, which is 10^(-5) * IDAPrice in MiniDollar, then we have
   176  	// 1 IDA = IDAPrice * 10^(-3) Dollar
   177  	// 1 IDA = IDAPrice * 10^(7) MiniDollar
   178  	// 10^(5) MiniIDA = IDAPrice * 10^(7) MiniDollar
   179  	// 1 MiniIDA = IDAPrice * 10^(2) * MiniDollar
   180  	miniIDAPrice := linotypes.NewMiniDollarFromInt(sdk.NewInt(idaPrice).MulRaw(100))
   181  	ida := model.AppIDA{
   182  		App:             appname,
   183  		Name:            idaName,
   184  		MiniIDAPrice:    miniIDAPrice,
   185  		IsRevoked:       false,
   186  		RevokeCoinPrice: linotypes.NewMiniDollar(0),
   187  	}
   188  	dm.storage.SetIDA(ctx, ida)
   189  	dm.storage.SetIDAStats(ctx, appname, model.AppIDAStats{
   190  		Total: linotypes.NewMiniDollar(0),
   191  	})
   192  	return nil
   193  }
   194  
   195  func (dm DeveloperManager) IDAConvertFromLino(ctx sdk.Context, username, appname linotypes.AccountKey, amount linotypes.Coin) sdk.Error {
   196  	if _, err := dm.validAppIDA(ctx, appname); err != nil {
   197  		return err
   198  	}
   199  	if !dm.acc.DoesAccountExist(ctx, username) || !dm.acc.DoesAccountExist(ctx, appname) {
   200  		return types.ErrAccountNotFound()
   201  	}
   202  
   203  	err := dm.acc.MoveCoin(ctx,
   204  		linotypes.NewAccOrAddrFromAcc(username),
   205  		linotypes.NewAccOrAddrFromAcc(appname),
   206  		amount)
   207  	if err != nil {
   208  		return err
   209  	}
   210  	miniDollar, err := dm.exchangeMiniDollar(ctx, appname, amount)
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	bank := dm.storage.GetIDABank(ctx, appname, username)
   216  	bank.Balance = bank.Balance.Plus(miniDollar)
   217  	dm.storage.SetIDABank(ctx, appname, username, bank)
   218  	return nil
   219  }
   220  
   221  // MintIDA - mint some IDA by converting LINO to IDA (internally MiniDollar).
   222  func (dm DeveloperManager) MintIDA(ctx sdk.Context, appname linotypes.AccountKey, amount linotypes.Coin) sdk.Error {
   223  	if _, err := dm.validAppIDA(ctx, appname); err != nil {
   224  		return err
   225  	}
   226  	bank := dm.storage.GetIDABank(ctx, appname, appname)
   227  	// ignored
   228  	// if bank.Unauthed {
   229  	// 	return types.ErrIDAUnauthed()
   230  	// }
   231  	miniDollar, err := dm.exchangeMiniDollar(ctx, appname, amount)
   232  	if err != nil {
   233  		return err
   234  	}
   235  	bank.Balance = bank.Balance.Plus(miniDollar)
   236  	dm.storage.SetIDABank(ctx, appname, appname, bank)
   237  	return nil
   238  }
   239  
   240  // exchangeMinidollar - exchange Minidollar with @p amount coin, return the exchanged minidollar.
   241  // Return minidollar must be added to somewhere, otherwise it's burned.
   242  // 1. move @p amount from account saving to reserve pool.
   243  func (dm DeveloperManager) exchangeMiniDollar(ctx sdk.Context, appname linotypes.AccountKey, amount linotypes.Coin) (linotypes.MiniDollar, sdk.Error) {
   244  	bought, err := dm.price.CoinToMiniDollar(ctx, amount)
   245  	if err != nil {
   246  		return linotypes.NewMiniDollar(0), err
   247  	}
   248  	if !bought.IsPositive() {
   249  		return linotypes.NewMiniDollar(0), types.ErrExchangeMiniDollarZeroAmount()
   250  	}
   251  	// exchange
   252  	err = dm.acc.MoveToPool(ctx,
   253  		linotypes.DevIDAReservePool, linotypes.NewAccOrAddrFromAcc(appname), amount)
   254  	if err != nil {
   255  		return linotypes.NewMiniDollar(0), err
   256  	}
   257  	pool := dm.storage.GetReservePool(ctx)
   258  	pool.Total = pool.Total.Plus(amount)
   259  	pool.TotalMiniDollar = pool.TotalMiniDollar.Plus(bought)
   260  	dm.storage.SetReservePool(ctx, pool)
   261  	idaStats := dm.storage.GetIDAStats(ctx, appname)
   262  	idaStats.Total = idaStats.Total.Plus(bought)
   263  	dm.storage.SetIDAStats(ctx, appname, *idaStats)
   264  	return bought, nil
   265  }
   266  
   267  // AppTransferIDA - transfer IDA back or from app, by app.
   268  func (dm DeveloperManager) AppTransferIDA(ctx sdk.Context, appname, signer linotypes.AccountKey, amount linotypes.MiniIDA, from, to linotypes.AccountKey) sdk.Error {
   269  	if !(from == appname || to == appname) {
   270  		return types.ErrInvalidTransferTarget()
   271  	}
   272  	ida, err := dm.validAppIDA(ctx, appname)
   273  	if err != nil {
   274  		return err
   275  	}
   276  	if !dm.acc.DoesAccountExist(ctx, from) || !dm.acc.DoesAccountExist(ctx, to) {
   277  		return types.ErrAccountNotFound()
   278  	}
   279  	// singer must be the affiliated of the app to be able to move IDAs.
   280  	signerApp, err := dm.GetAffiliatingApp(ctx, signer)
   281  	if err != nil || signerApp != appname {
   282  		return types.ErrInvalidSigner()
   283  	}
   284  	minidollar := linotypes.MiniIDAToMiniDollar(amount, ida.MiniIDAPrice)
   285  	return dm.appIDAMove(ctx, appname, from, to, minidollar)
   286  }
   287  
   288  // MoveIDA - app move ida, authorization check applied.
   289  // 1. amount must > 0.
   290  // 2. from's bank is not frozen.
   291  func (dm DeveloperManager) MoveIDA(ctx sdk.Context, app, from, to linotypes.AccountKey, amount linotypes.MiniDollar) sdk.Error {
   292  	if _, err := dm.validAppIDA(ctx, app); err != nil {
   293  		return err
   294  	}
   295  	if !dm.acc.DoesAccountExist(ctx, from) || !dm.acc.DoesAccountExist(ctx, to) {
   296  		return types.ErrAccountNotFound()
   297  	}
   298  	return dm.appIDAMove(ctx, app, from, to, amount)
   299  }
   300  
   301  func (dm DeveloperManager) validAppIDA(ctx sdk.Context, app linotypes.AccountKey) (*model.AppIDA, sdk.Error) {
   302  	if !dm.DoesDeveloperExist(ctx, app) {
   303  		return nil, types.ErrDeveloperNotFound()
   304  	}
   305  	ida, err := dm.storage.GetIDA(ctx, app)
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  	if ida.IsRevoked {
   310  		return nil, types.ErrIDARevoked()
   311  	}
   312  	return ida, nil
   313  }
   314  
   315  // appIDAMove - authorization check applied. app is not checked.
   316  func (dm DeveloperManager) appIDAMove(ctx sdk.Context, app, from, to linotypes.AccountKey, amount linotypes.MiniDollar) sdk.Error {
   317  	if !amount.IsPositive() {
   318  		return linotypes.ErrInvalidIDAAmount()
   319  	}
   320  	fromBank := dm.storage.GetIDABank(ctx, app, from)
   321  	toBank := dm.storage.GetIDABank(ctx, app, to)
   322  	if fromBank.Unauthed {
   323  		return types.ErrIDAUnauthed()
   324  	}
   325  	if fromBank.Balance.LT(amount) {
   326  		return types.ErrNotEnoughIDA()
   327  	}
   328  	fromBank.Balance = fromBank.Balance.Minus(amount)
   329  	toBank.Balance = toBank.Balance.Plus(amount)
   330  	dm.storage.SetIDABank(ctx, app, from, fromBank)
   331  	dm.storage.SetIDABank(ctx, app, to, toBank)
   332  	return nil
   333  }
   334  
   335  func (dm DeveloperManager) GetMiniIDAPrice(ctx sdk.Context, app linotypes.AccountKey) (linotypes.MiniDollar, sdk.Error) {
   336  	ida, err := dm.validAppIDA(ctx, app)
   337  	if err != nil {
   338  		return linotypes.NewMiniDollar(0), err
   339  	}
   340  	return ida.MiniIDAPrice, nil
   341  }
   342  
   343  // BurnIDA - Burn some @p amount of IDA on @p user's account, burned IDA will be converted to
   344  // LINO and saved onto the user's account. Return the amount of coins
   345  // removed from reserve pool. The coins can has been . NOTE: cannot burn @p amount so little
   346  // that can only can only buy less than 1 coin, i.e. zero.
   347  func (dm DeveloperManager) BurnIDA(ctx sdk.Context, app, user linotypes.AccountKey, amount linotypes.MiniDollar) (linotypes.Coin, sdk.Error) {
   348  	bank, err := dm.GetIDABank(ctx, app, user)
   349  	if err != nil {
   350  		return linotypes.NewCoinFromInt64(0), err
   351  	}
   352  	if bank.Unauthed {
   353  		return linotypes.NewCoinFromInt64(0), types.ErrIDAUnauthed()
   354  	}
   355  	if bank.Balance.LT(amount) {
   356  		return linotypes.NewCoinFromInt64(0), types.ErrNotEnoughIDA()
   357  	}
   358  	bought, used, err := dm.price.MiniDollarToCoin(ctx, amount)
   359  	if err != nil {
   360  		return linotypes.NewCoinFromInt64(0), err
   361  	}
   362  	if !bought.IsPositive() {
   363  		return linotypes.NewCoinFromInt64(0), types.ErrBurnZeroIDA()
   364  	}
   365  
   366  	// internal reserve pool check
   367  	pool := dm.storage.GetReservePool(ctx)
   368  	if !pool.Total.IsGTE(bought) {
   369  		return linotypes.NewCoinFromInt64(0), types.ErrInsuffientReservePool()
   370  	}
   371  	pool.Total = pool.Total.Minus(bought)
   372  	pool.TotalMiniDollar = pool.TotalMiniDollar.Minus(used)
   373  	dm.storage.SetReservePool(ctx, pool)
   374  
   375  	// ida stats update
   376  	idaStats := dm.storage.GetIDAStats(ctx, app)
   377  	idaStats.Total = idaStats.Total.Minus(used)
   378  	dm.storage.SetIDAStats(ctx, app, *idaStats)
   379  
   380  	// ida bank update
   381  	bank.Balance = bank.Balance.Minus(used)
   382  	dm.storage.SetIDABank(ctx, app, user, &bank)
   383  
   384  	// external
   385  	// after burn, move coins from the reserve pool to the user's account.
   386  	// only called upon donation, so the newly added coins will then be moved
   387  	// to the vote's frictions pool.
   388  	err = dm.acc.MoveFromPool(ctx,
   389  		linotypes.DevIDAReservePool, linotypes.NewAccOrAddrFromAcc(user), bought)
   390  	if err != nil {
   391  		return linotypes.NewCoinFromInt64(0), err
   392  	}
   393  
   394  	return bought, nil
   395  }
   396  
   397  func (dm DeveloperManager) GetIDA(ctx sdk.Context, app linotypes.AccountKey) (model.AppIDA, sdk.Error) {
   398  	ida, err := dm.validAppIDA(ctx, app)
   399  	if err != nil {
   400  		return model.AppIDA{}, err
   401  	}
   402  	return *ida, err
   403  }
   404  
   405  func (dm DeveloperManager) GetIDABank(ctx sdk.Context, app, user linotypes.AccountKey) (model.IDABank, sdk.Error) {
   406  	if !dm.DoesDeveloperExist(ctx, app) {
   407  		return model.IDABank{}, types.ErrDeveloperNotFound()
   408  	}
   409  	if !dm.acc.DoesAccountExist(ctx, user) {
   410  		return model.IDABank{}, types.ErrAccountNotFound()
   411  	}
   412  	return *dm.storage.GetIDABank(ctx, app, user), nil
   413  }
   414  
   415  // UpdateAffiliated - add or remove an affiliated account.
   416  func (dm DeveloperManager) UpdateAffiliated(ctx sdk.Context, appname, username linotypes.AccountKey, activate bool) sdk.Error {
   417  	if !dm.DoesDeveloperExist(ctx, appname) {
   418  		return types.ErrDeveloperNotFound()
   419  	}
   420  	if !dm.acc.DoesAccountExist(ctx, username) {
   421  		return types.ErrAccountNotFound()
   422  	}
   423  	app, err := dm.storage.GetDeveloper(ctx, appname)
   424  	if err != nil {
   425  		return err
   426  	}
   427  	if app.NAffiliated >= maxAffiliatedAccount {
   428  		return types.ErrMaxAffiliatedExceeded()
   429  	}
   430  	if activate {
   431  		err := dm.addAffiliated(ctx, appname, username)
   432  		if err != nil {
   433  			return err
   434  		}
   435  		app.NAffiliated += 1
   436  		dm.storage.SetDeveloper(ctx, *app)
   437  	} else {
   438  		err := dm.removeAffiliated(ctx, appname, username)
   439  		if err != nil {
   440  			return err
   441  		}
   442  		app.NAffiliated -= 1
   443  		dm.storage.SetDeveloper(ctx, *app)
   444  	}
   445  	return nil
   446  }
   447  
   448  // To activate an affiliated account, check:
   449  // 1. not affiliated to any developer.
   450  // 2. not a developer.
   451  // 3. not on any other duty.
   452  func (dm DeveloperManager) addAffiliated(ctx sdk.Context, app, username linotypes.AccountKey) sdk.Error {
   453  	if dm.storage.HasUserRole(ctx, username) {
   454  		return types.ErrInvalidAffiliatedAccount("is affiliated already")
   455  	}
   456  	// TODO(@yumin): Do we check if username as developer is deleted already?
   457  	if dm.storage.HasDeveloper(ctx, username) {
   458  		return types.ErrInvalidAffiliatedAccount("is/was developer")
   459  	}
   460  	duty, err := dm.vote.GetVoterDuty(ctx, username)
   461  	if err == nil && duty != votetypes.DutyVoter {
   462  		return types.ErrInvalidAffiliatedAccount("on duty of something else")
   463  	}
   464  	dm.storage.SetAffiliatedAcc(ctx, app, username)
   465  	dm.storage.SetUserRole(ctx, username, &model.Role{
   466  		AffiliatedApp: app,
   467  	})
   468  	return nil
   469  }
   470  
   471  // To remove an affiliated account from app
   472  // 1. user is the affiliated account of app.
   473  func (dm DeveloperManager) removeAffiliated(ctx sdk.Context, app, username linotypes.AccountKey) sdk.Error {
   474  	role, err := dm.storage.GetUserRole(ctx, username)
   475  	if err != nil {
   476  		return err
   477  	}
   478  	if role.AffiliatedApp != app {
   479  		return types.ErrInvalidAffiliatedAccount("not affiliated account of provided app")
   480  	}
   481  	dm.storage.DelAffiliatedAcc(ctx, app, username)
   482  	dm.storage.DelUserRole(ctx, username)
   483  	return nil
   484  }
   485  
   486  // GetAffiliatingApp - get username's affiliating app, or username itself is an app.
   487  func (dm DeveloperManager) GetAffiliatingApp(ctx sdk.Context, username linotypes.AccountKey) (linotypes.AccountKey, sdk.Error) {
   488  	// username is app itself.
   489  	if dm.DoesDeveloperExist(ctx, username) {
   490  		return username, nil
   491  	}
   492  	// user's role.
   493  	role, err := dm.storage.GetUserRole(ctx, username)
   494  	if err != nil {
   495  		return "", err
   496  	}
   497  	return role.AffiliatedApp, nil
   498  }
   499  
   500  // GetAffiliated returns all affiliated account of app.
   501  func (dm DeveloperManager) GetAffiliated(ctx sdk.Context, app linotypes.AccountKey) []linotypes.AccountKey {
   502  	if !dm.DoesDeveloperExist(ctx, app) {
   503  		return nil
   504  	}
   505  	return dm.storage.GetAllAffiliatedAcc(ctx, app)
   506  }
   507  
   508  // UpdateAuthorization - update app's authorization on user.
   509  func (dm DeveloperManager) UpdateIDAAuth(ctx sdk.Context, app, username linotypes.AccountKey, active bool) sdk.Error {
   510  	// when developer is revoked, no need to update auth
   511  	if !dm.DoesDeveloperExist(ctx, app) {
   512  		return types.ErrDeveloperNotFound()
   513  	}
   514  	if !dm.acc.DoesAccountExist(ctx, username) {
   515  		return types.ErrAccountNotFound()
   516  	}
   517  	if dm.storage.HasAffiliatedAcc(ctx, app, username) {
   518  		return types.ErrInvalidIDAAuth()
   519  	}
   520  	bank := dm.storage.GetIDABank(ctx, app, username)
   521  	if bank.Unauthed == !active {
   522  		return types.ErrInvalidIDAAuth()
   523  	}
   524  	bank.Unauthed = !active
   525  	dm.storage.SetIDABank(ctx, app, username, bank)
   526  	return nil
   527  }
   528  
   529  // ReportConsumption - add consumption to a developer.
   530  func (dm DeveloperManager) ReportConsumption(ctx sdk.Context, app linotypes.AccountKey, consumption linotypes.MiniDollar) sdk.Error {
   531  	developer, err := dm.storage.GetDeveloper(ctx, app)
   532  	if err != nil {
   533  		return err
   534  	}
   535  	developer.AppConsumption = developer.AppConsumption.Plus(consumption)
   536  	dm.storage.SetDeveloper(ctx, *developer)
   537  	return nil
   538  }
   539  
   540  // DistributeDevInflation - distribute monthly app inflation.
   541  func (dm DeveloperManager) MonthlyDistributeDevInflation(ctx sdk.Context) sdk.Error {
   542  	// No-op if there is no developer, leave inflations in pool.
   543  	devs := dm.GetLiveDevelopers(ctx)
   544  	if len(devs) == 0 {
   545  		return nil
   546  	}
   547  	inflation, err := dm.acc.GetPool(ctx, linotypes.InflationDeveloperPool)
   548  	if err != nil {
   549  		return err
   550  	}
   551  	distSchema := make([]sdk.Dec, len(devs))
   552  	totalConsumption := linotypes.NewMiniDollar(0)
   553  	for _, dev := range devs {
   554  		totalConsumption = totalConsumption.Plus(dev.AppConsumption)
   555  	}
   556  	if totalConsumption.IsZero() {
   557  		// if not any consumption here, we evenly distribute all inflation
   558  		for i := range devs {
   559  			distSchema[i] = linotypes.NewDecFromRat(1, int64(len(devs)))
   560  		}
   561  	} else {
   562  		for i, dev := range devs {
   563  			distSchema[i] = dev.AppConsumption.ToDec().Quo(totalConsumption.ToDec())
   564  		}
   565  	}
   566  
   567  	distributed := linotypes.NewCoinFromInt64(0)
   568  	for i, developer := range devs {
   569  		if i == (len(devs) - 1) {
   570  			if err := dm.acc.MoveFromPool(ctx, linotypes.InflationDeveloperPool,
   571  				linotypes.NewAccOrAddrFromAcc(developer.Username),
   572  				inflation.Minus(distributed)); err != nil {
   573  				return err
   574  			}
   575  			break
   576  		}
   577  		percentage := distSchema[i]
   578  		myShareRat := inflation.ToDec().Mul(percentage)
   579  		myShareCoin := linotypes.DecToCoin(myShareRat)
   580  		distributed = distributed.Plus(myShareCoin)
   581  		if err := dm.acc.MoveFromPool(ctx, linotypes.InflationDeveloperPool,
   582  			linotypes.NewAccOrAddrFromAcc(developer.Username),
   583  			myShareCoin); err != nil {
   584  			return err
   585  		}
   586  	}
   587  
   588  	dm.clearConsumption(ctx)
   589  	return nil
   590  }
   591  
   592  func (dm DeveloperManager) clearConsumption(ctx sdk.Context) {
   593  	devs := dm.GetLiveDevelopers(ctx)
   594  	for _, dev := range devs {
   595  		dev.AppConsumption = linotypes.NewMiniDollar(0)
   596  		dm.storage.SetDeveloper(ctx, dev)
   597  	}
   598  }
   599  
   600  func (dm DeveloperManager) GetReservePool(ctx sdk.Context) model.ReservePool {
   601  	return *dm.storage.GetReservePool(ctx)
   602  }
   603  
   604  func (dm DeveloperManager) GetIDAStats(ctx sdk.Context, app linotypes.AccountKey) (model.AppIDAStats, sdk.Error) {
   605  	if _, err := dm.validAppIDA(ctx, app); err != nil {
   606  		return model.AppIDAStats{}, err
   607  	}
   608  	stats := *dm.storage.GetIDAStats(ctx, app)
   609  	return stats, nil
   610  }
   611  
   612  func (dm DeveloperManager) ExportToFile(ctx sdk.Context, cdc *codec.Codec, filepath string) error {
   613  	state := &model.DeveloperTablesIR{
   614  		Version: exportVersion,
   615  	}
   616  	stores := dm.storage.StoreMap(ctx)
   617  
   618  	// export developers
   619  	stores[string(model.DeveloperSubstore)].Iterate(func(key []byte, val interface{}) bool {
   620  		dev := val.(*model.Developer)
   621  		state.Developers = append(state.Developers, model.DeveloperIR{
   622  			Username:       dev.Username,
   623  			AppConsumption: dev.AppConsumption,
   624  			Website:        dev.Website,
   625  			Description:    dev.Description,
   626  			AppMetaData:    dev.AppMetaData,
   627  			IsDeleted:      dev.IsDeleted,
   628  			NAffiliated:    dev.NAffiliated,
   629  		})
   630  		return false
   631  	})
   632  
   633  	// export IDAs
   634  	stores[string(model.IdaSubstore)].Iterate(func(key []byte, val interface{}) bool {
   635  		ida := val.(*model.AppIDA)
   636  		state.IDAs = append(state.IDAs, model.AppIDAIR(*ida))
   637  		return false
   638  	})
   639  
   640  	// export ida balance
   641  	stores[string(model.IdaBalanceSubstore)].Iterate(func(key []byte, val interface{}) bool {
   642  		app, user := model.ParseIDABalanceKey(key)
   643  		bank := val.(*model.IDABank)
   644  		state.IDABanks = append(state.IDABanks, model.IDABankIR{
   645  			App:      app,
   646  			User:     user,
   647  			Balance:  bank.Balance,
   648  			Unauthed: bank.Unauthed,
   649  		})
   650  		return false
   651  	})
   652  
   653  	// export reserve pool
   654  	stores[string(model.ReservePoolSubstore)].Iterate(func(key []byte, val interface{}) bool {
   655  		pool := val.(*model.ReservePool)
   656  		state.ReservePool = model.ReservePoolIR(*pool)
   657  		return false
   658  	})
   659  
   660  	// export affiliated accounts
   661  	stores[string(model.AffiliatedAccSubstore)].Iterate(func(key []byte, _ interface{}) bool {
   662  		app, user := model.ParseAffiliatedAccKey(key)
   663  		state.AffiliatedAccs = append(state.AffiliatedAccs, model.AffiliatedAccIR{
   664  			App:  app,
   665  			User: user,
   666  		})
   667  		return false
   668  	})
   669  
   670  	// export UserRoles
   671  	stores[string(model.UserRoleSubstore)].Iterate(func(key []byte, val interface{}) bool {
   672  		role := val.(*model.Role)
   673  		state.UserRoles = append(state.UserRoles, model.UserRoleIR{
   674  			User:          linotypes.AccountKey(key),
   675  			AffiliatedApp: role.AffiliatedApp,
   676  		})
   677  		return false
   678  	})
   679  
   680  	// export IDA stats
   681  	stores[string(model.IdaStatsSubstore)].Iterate(func(key []byte, val interface{}) bool {
   682  		stats := val.(*model.AppIDAStats)
   683  		state.IDAStats = append(state.IDAStats, model.IDAStatsIR{
   684  			App:   linotypes.AccountKey(key),
   685  			Total: stats.Total,
   686  		})
   687  		return false
   688  	})
   689  
   690  	return utils.Save(filepath, cdc, state)
   691  }
   692  
   693  // Import from file
   694  func (dm DeveloperManager) ImportFromFile(ctx sdk.Context, cdc *codec.Codec, filepath string) error {
   695  	rst, err := utils.Load(filepath, cdc, func() interface{} { return &model.DeveloperTablesIR{} })
   696  	if err != nil {
   697  		return err
   698  	}
   699  	table := rst.(*model.DeveloperTablesIR)
   700  
   701  	if table.Version != importVersion {
   702  		return fmt.Errorf("unsupported import version: %d", table.Version)
   703  	}
   704  
   705  	// import developers
   706  	for _, dev := range table.Developers {
   707  		dm.storage.SetDeveloper(ctx, model.Developer{
   708  			Username:       dev.Username,
   709  			AppConsumption: dev.AppConsumption,
   710  			Website:        dev.Website,
   711  			Description:    dev.Description,
   712  			AppMetaData:    dev.AppMetaData,
   713  			IsDeleted:      dev.IsDeleted,
   714  			NAffiliated:    dev.NAffiliated,
   715  		})
   716  	}
   717  
   718  	// import IDAs
   719  	for _, ida := range table.IDAs {
   720  		dm.storage.SetIDA(ctx, model.AppIDA(ida))
   721  	}
   722  
   723  	// import IDABanks
   724  	for _, bank := range table.IDABanks {
   725  		dm.storage.SetIDABank(ctx, bank.App, bank.User, &model.IDABank{
   726  			Balance:  bank.Balance,
   727  			Unauthed: bank.Unauthed,
   728  		})
   729  	}
   730  
   731  	// import reserve pool
   732  	pool := model.ReservePool(table.ReservePool)
   733  	dm.storage.SetReservePool(ctx, &pool)
   734  
   735  	// import affiliated accounts
   736  	for _, acc := range table.AffiliatedAccs {
   737  		dm.storage.SetAffiliatedAcc(ctx, acc.App, acc.User)
   738  	}
   739  
   740  	// import user roles
   741  	for _, role := range table.UserRoles {
   742  		dm.storage.SetUserRole(ctx, role.User, &model.Role{
   743  			AffiliatedApp: role.AffiliatedApp,
   744  		})
   745  	}
   746  
   747  	// import ida stats
   748  	for _, stat := range table.IDAStats {
   749  		dm.storage.SetIDAStats(ctx, stat.App, model.AppIDAStats{
   750  			Total: stat.Total,
   751  		})
   752  	}
   753  
   754  	return nil
   755  }