diff --git a/api/api.go b/api/api.go index 2bf2ecd..0f57140 100644 --- a/api/api.go +++ b/api/api.go @@ -228,7 +228,7 @@ func (capi *census3API) CreateInitialTokens(tokensPath string) error { return err } defer func() { - if err := tx.Rollback(); err != nil && !errors.Is(sql.ErrTxDone, err) { + if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) { log.Errorw(err, "create token transaction rollback failed") } }() diff --git a/api/censuses.go b/api/censuses.go index 1f1bf0b..6227e9f 100644 --- a/api/censuses.go +++ b/api/censuses.go @@ -122,7 +122,7 @@ func (capi *census3API) createAndPublishCensus(req *Census, qID string) (uint64, return 0, ErrCantCreateCensus.WithErr(err) } defer func() { - if err := tx.Rollback(); err != nil && !errors.Is(sql.ErrTxDone, err) { + if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) { log.Errorw(err, "holders transaction rollback failed") } }() @@ -154,7 +154,7 @@ func (capi *census3API) createAndPublishCensus(req *Census, qID string) (uint64, // init some variables to get computed in the following steps calculateStrategyProgress := capi.queue.StepProgressChannel(qID, 1, 3) strategyHolders, censusWeight, totalTokensBlockNumber, err := capi.CalculateStrategyHolders( - internalCtx, strategy.Predicate, strategyTokensBySymbol, calculateStrategyProgress) + internalCtx, strategy.Predicate, strategyTokensBySymbol, calculateStrategyProgress, false) if err != nil { return 0, ErrEvalStrategyPredicate.WithErr(err) } diff --git a/api/helpers.go b/api/helpers.go index c6c9581..adb4374 100644 --- a/api/helpers.go +++ b/api/helpers.go @@ -235,6 +235,7 @@ func InnerCensusID(blockNumber, strategyID uint64, anonymous bool) uint64 { // combines them. func (capi *census3API) CalculateStrategyHolders(ctx context.Context, predicate string, tokens map[string]*StrategyToken, progressCh chan float64, + truncateByDecimals bool, ) (map[common.Address]*big.Int, *big.Int, uint64, error) { // TODO: write a benchmark and try to optimize this function @@ -297,6 +298,7 @@ func (capi *census3API) CalculateStrategyHolders(ctx context.Context, if token == nil { return nil, nil, totalTokensBlockNumber, fmt.Errorf("token not found for predicate: %s", validPredicate.String()) } + // get the strategy holders from the database holders, err := capi.db.QueriesRO.TokenHoldersByMinBalance(ctx, queries.TokenHoldersByMinBalanceParams{ @@ -311,6 +313,13 @@ func (capi *census3API) CalculateStrategyHolders(ctx context.Context, } return nil, nil, totalTokensBlockNumber, err } + + // Convert decimals to *big.Int by calculating 10^decimals + decimalsBigInt := new(big.Int).SetUint64(0) + if ti, ok := tokensInfo[validPredicate.String()]; ok && truncateByDecimals { + decimalsBigInt = new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(ti.Decimals)), nil) + } + // parse holders addresses and balances for _, holder := range holders { holderAddr := common.BytesToAddress(holder.HolderID) @@ -318,6 +327,11 @@ func (capi *census3API) CalculateStrategyHolders(ctx context.Context, if !ok { return nil, nil, totalTokensBlockNumber, fmt.Errorf("error decoding balance of holder %s", holderAddr.String()) } + // truncate the balance by the decimals if required + if truncateByDecimals && decimalsBigInt.Uint64() > 0 { + holderBalance = new(big.Int).Div(holderBalance, decimalsBigInt) + } + if _, exists := strategyHolders[holderAddr]; !exists { strategyHolders[holderAddr] = holderBalance censusWeight = new(big.Int).Add(censusWeight, holderBalance) @@ -332,6 +346,9 @@ func (capi *census3API) CalculateStrategyHolders(ctx context.Context, if err != nil { return nil, nil, totalTokensBlockNumber, err } + + // TODO: the truncateByDEcimals is not implemented for complex predicates + // parse the evaluation results for address, value := range res.Data { strategyHolders[common.HexToAddress(address)] = value diff --git a/api/strategies.go b/api/strategies.go index 3182784..4fe6adc 100644 --- a/api/strategies.go +++ b/api/strategies.go @@ -97,7 +97,7 @@ func (capi *census3API) getStrategies(msg *api.APIdata, ctx *httprouter.HTTPCont return ErrCantGetStrategies.WithErr(err) } defer func() { - if err := tx.Rollback(); err != nil && !errors.Is(sql.ErrTxDone, err) { + if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) { log.Errorw(err, "create strategy transaction rollback failed") } }() @@ -195,7 +195,7 @@ func (capi *census3API) createStrategy(msg *api.APIdata, ctx *httprouter.HTTPCon return ErrCantCreateStrategy.WithErr(err) } defer func() { - if err := tx.Rollback(); err != nil && !errors.Is(sql.ErrTxDone, err) { + if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) { log.Errorw(err, "create strategy transaction rollback failed") } }() @@ -368,7 +368,7 @@ func (capi *census3API) importStrategyDump(ipfsURI string, dump []byte) (uint64, return 0, ErrCantCreateStrategy.WithErr(err) } defer func() { - if err := tx.Rollback(); err != nil && !errors.Is(sql.ErrTxDone, err) { + if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) { log.Errorw(err, "create strategy transaction rollback failed") } }() @@ -561,6 +561,10 @@ func (capi *census3API) launchStrategyHolders(_ *api.APIdata, ctx *httprouter.HT return ErrMalformedStrategyID.WithErr(err) } strategyID := uint64(iStrategyID) + + // get truncateByDecimals from query params and decode it as boolean + truncateByDecimals := ctx.Request.URL.Query().Get("truncateByDecimals") == "true" + // get token information from the database checkCtx, cancel := context.WithTimeout(ctx.Request.Context(), checkStrategyHoldersTimeout) defer cancel() @@ -597,7 +601,7 @@ func (capi *census3API) launchStrategyHolders(_ *api.APIdata, ctx *httprouter.HT } } strategyHolders, _, _, err := capi.CalculateStrategyHolders( - bgCtx, strategy.Predicate, strategyTokensBySymbol, nil) + bgCtx, strategy.Predicate, strategyTokensBySymbol, nil, truncateByDecimals) if err != nil { if ok := capi.queue.Fail(queueID, ErrEvalStrategyPredicate.WithErr(err)); !ok { log.Errorf("error updating list strategy holders queue %s", queueID) @@ -713,7 +717,7 @@ func (capi *census3API) estimateStrategySizeAndAccuracy(queueID string, } // calculate the strategy holders strategyHolders, _, _, err := capi.CalculateStrategyHolders(internalCtx, - strategy.Predicate, strategy.Tokens, calculateStrategyProgress) + strategy.Predicate, strategy.Tokens, calculateStrategyProgress, false) if err != nil { return 0, 0, ErrEvalStrategyPredicate.WithErr(err) } diff --git a/api/tokens.go b/api/tokens.go index 2dc6c88..6695761 100644 --- a/api/tokens.go +++ b/api/tokens.go @@ -288,7 +288,7 @@ func (capi *census3API) createToken(msg *api.APIdata, ctx *httprouter.HTTPContex return ErrCantCreateStrategy.WithErr(err) } defer func() { - if err := tx.Rollback(); err != nil && !errors.Is(sql.ErrTxDone, err) { + if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) { log.Errorw(err, "create strategy transaction rollback failed") } }() @@ -360,7 +360,7 @@ func (capi *census3API) deleteToken(address common.Address, chainID uint64, exte return ErrCantGetTokens.WithErr(err) } defer func() { - if err := tx.Rollback(); err != nil && !errors.Is(sql.ErrTxDone, err) { + if err := tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) { log.Errorw(err, "error rolling back tokens transaction") } }() diff --git a/apiclient/routes.go b/apiclient/routes.go index 4d70fbd..ff9170c 100644 --- a/apiclient/routes.go +++ b/apiclient/routes.go @@ -33,7 +33,7 @@ const ( // parameters CreateStrategyURI = "/strategies" // GetTokenHoldersByStrategyURI is the URI for getting token holders of a given strategy - GetTokenHoldersByStrategyURI = "/strategies/%d/holders" + GetTokenHoldersByStrategyURI = "/strategies/%d/holders?truncateByDecimals=%s" // GetTokenHoldersByStrategyURI is the URI for getting token holders of a given strategy GetTokenHoldersByStrategyQueueURI = "/strategies/%d/holders/queue/%s" diff --git a/apiclient/strategies.go b/apiclient/strategies.go index 699f11d..58b754c 100644 --- a/apiclient/strategies.go +++ b/apiclient/strategies.go @@ -85,9 +85,11 @@ func (c *HTTPclient) Strategy(strategyID uint64) (*api.Strategy, error) { // receives the strategyID and returns the queueID of the task and an error if // something went wrong. The status of the task can be checked with the // HoldersByStrategyQueue method. -func (c *HTTPclient) HoldersByStrategy(strategyID uint64) (string, error) { +// If truncateByDecimals is true, the amounts will be truncated by the decimals +// of the token (if > 0). Truncate only works for simple strategies (single token). +func (c *HTTPclient) HoldersByStrategy(strategyID uint64, truncateByDecimals bool) (string, error) { // construct the URL to the API with the given parameters - endpoint := fmt.Sprintf(GetTokenHoldersByStrategyURI, strategyID) + endpoint := fmt.Sprintf(GetTokenHoldersByStrategyURI, strategyID, fmt.Sprintf("%t", truncateByDecimals)) u, err := c.constructURL(endpoint) if err != nil { return "", fmt.Errorf("%w: %w", ErrConstructingURL, err) @@ -193,8 +195,10 @@ func (c *HTTPclient) HoldersByStrategyQueue(strategyID uint64, queueID string) ( // strategy, it receives the strategyID and returns a map of addresses and // amounts and an error if something went wrong. This method is a wrapper // around the HoldersByStrategy and HoldersByStrategyQueue methods. -func (c *HTTPclient) AllHoldersByStrategy(strategyID uint64) (map[common.Address]*big.Int, error) { - queueID, err := c.HoldersByStrategy(strategyID) +// If truncateByDecimals is true, the amounts will be truncated by the decimals +// of the token (if > 0). Truncate only works for simple strategies (single token). +func (c *HTTPclient) AllHoldersByStrategy(strategyID uint64, truncateByDecimals bool) (map[common.Address]*big.Int, error) { + queueID, err := c.HoldersByStrategy(strategyID, truncateByDecimals) if err != nil { return nil, err }