From 4524f905ad4f373c7d9e44a1ea4a806a2e6731bb Mon Sep 17 00:00:00 2001 From: Lucas Menendez Date: Wed, 27 Sep 2023 21:03:15 +0200 Subject: [PATCH] new endpoint to check that a address is registered as a holder for a token, some comments and typos --- api/README.md | 4 +-- api/errors.go | 5 ++++ api/strategyoperators/combinators.go | 19 ++++++++++++++ api/strategyoperators/strategyoperators.go | 30 +++++++++++++--------- api/tokens.go | 26 +++++++++++++++++++ census/census.go | 2 +- db/queries/holders.sql | 9 +++++++ db/sqlc/holders.sql.go | 23 +++++++++++++++++ 8 files changed, 103 insertions(+), 15 deletions(-) diff --git a/api/README.md b/api/README.md index a07543e9..52ba80cb 100644 --- a/api/README.md +++ b/api/README.md @@ -510,7 +510,7 @@ Returns the information of the snapshots related to the provided ID. | 500 | `error getting census information` | 5009 | | 500 | `error encoding census` | 5017 | -### GET `/census/queue/{queueID}` +### GET `/censuses/queue/{queueID}` Returns the information of the census that are in the creation queue. - 📥 response: @@ -549,7 +549,7 @@ Returns the information of the census that are in the creation queue. | 500 | `error creating the census tree on the census database` | 5001 | | 500 | `error evaluating strategy predicate` | 5026 | -### GET `/census/strategy/{strategyID}` +### GET `/censuses/strategy/{strategyID}` Returns a list of censusID for the strategy provided. - 📥 response: diff --git a/api/errors.go b/api/errors.go index 6612e0f0..56014b24 100644 --- a/api/errors.go +++ b/api/errors.go @@ -98,6 +98,11 @@ var ( HTTPstatus: apirest.HTTPstatusNoContent, Err: fmt.Errorf("strategy has not registered holders"), } + ErrMalformedChainID = apirest.APIerror{ + Code: 4018, + HTTPstatus: apirest.HTTPstatusBadRequest, + Err: fmt.Errorf("malformed chain ID"), + } ErrCantCreateToken = apirest.APIerror{ Code: 5000, HTTPstatus: apirest.HTTPstatusInternalErr, diff --git a/api/strategyoperators/combinators.go b/api/strategyoperators/combinators.go index ad48ba42..c922a703 100644 --- a/api/strategyoperators/combinators.go +++ b/api/strategyoperators/combinators.go @@ -2,6 +2,10 @@ package strategyoperators import "math/big" +// normalize returns the two balances with the same number of decimals. It also +// returns the number of decimals used to normalize these numbers. To choose the +// correct number of decimals, the function chooses the highest number of +// decimals between the two provided values. func normalize(a, b *big.Int, aDecimals, bDecimals uint64) (*big.Int, *big.Int, uint64) { if aDecimals > bDecimals { exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(aDecimals-bDecimals)), nil) @@ -11,11 +15,17 @@ func normalize(a, b *big.Int, aDecimals, bDecimals uint64) (*big.Int, *big.Int, return new(big.Int).Mul(a, exp), b, bDecimals } +// reduceNormalized returns the balance provided reducing the number of decimals +// of it by the number of decimals provided. It allows to fix the normalization +// of a balance after operations like multiplication or division. func reduceNormalized(a *big.Int, aDecimals uint64) *big.Int { exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(aDecimals)), nil) return new(big.Int).Div(a, exp) } +// sumBalancesCombinator returns the sum of the balances provided for each +// address in the provided map. It returns a new map with the same keys and the +// result of the sum of the balances. func sumBalancesCombinator(balances map[string][2]*big.Int) map[string]*big.Int { res := make(map[string]*big.Int) for address, balances := range balances { @@ -24,6 +34,12 @@ func sumBalancesCombinator(balances map[string][2]*big.Int) map[string]*big.Int return res } +// mulBalancesCombinator returns the multiplication of the balances provided for +// each address in the provided map. If the forceNotZero flag is set to true, +// and any of the balances is zero, the address is not included in the result. +// Else if forceNotZero is set to false, and any of the balances is zero, the +// other one will be assigned to the address. The resulting balances are reduced +// by the number of decimals provided using the reduceNormalized function. func mulBalancesCombinator(balances map[string][2]*big.Int, decimals uint64, forceNotZero bool) map[string]*big.Int { res := make(map[string]*big.Int) for address, balances := range balances { @@ -51,6 +67,9 @@ func mulBalancesCombinator(balances map[string][2]*big.Int, decimals uint64, for return res } +// membershipCombinator returns a map with the same keys as the provided map, +// and the value of each key is 1, discarding the value of the balances of the +// provided map. func membershipCombinator(balances map[string][2]*big.Int) map[string]*big.Int { res := make(map[string]*big.Int) for address := range balances { diff --git a/api/strategyoperators/strategyoperators.go b/api/strategyoperators/strategyoperators.go index a68c2523..6a89b9ae 100644 --- a/api/strategyoperators/strategyoperators.go +++ b/api/strategyoperators/strategyoperators.go @@ -34,28 +34,34 @@ var ValidOperatorsTags = []string{ // ValidOperators variable contains the information of the supported operators var ValidOperators = []map[string]string{ { - "tag": ANDTag, - "description": "logical operator that returns the common token holders between symbols with fixed balance to 1", + "tag": ANDTag, + "description": "logical operator that returns the common token " + + "holders between symbols with fixed balance to 1", }, { - "tag": ANDSUMTag, - "description": "logical operator that returns the common token holders between symbols with the sum of their balances on both tokens", + "tag": ANDSUMTag, + "description": "logical operator that returns the common token holders " + + "between symbols with the sum of their balances on both tokens", }, { - "tag": ANDMULTag, - "description": "logical operator that returns the common token holders between symbols with the multiplication of their balances on both tokens", + "tag": ANDMULTag, + "description": "logical operator that returns the common token holders " + + "between symbols with the multiplication of their balances on both tokens", }, { - "tag": ORTag, - "description": "logical operator that returns the token holders of both symbols with fixed balance to 1", + "tag": ORTag, + "description": "logical operator that returns the token holders of " + + "both symbols with fixed balance to 1", }, { - "tag": ORSUMTag, - "description": "logical operator that returns the token holders of both symbols with the sum of their balances on both tokens", + "tag": ORSUMTag, + "description": "logical operator that returns the token holders of " + + "both symbols with the sum of their balances on both tokens", }, { - "tag": ORMULTag, - "description": "logical operator that returns the token holders of both symbols with the multiplication of their balances on both tokens", + "tag": ORMULTag, + "description": "logical operator that returns the token holders of " + + "both symbols with the multiplication of their balances on both tokens", }, } diff --git a/api/tokens.go b/api/tokens.go index 9cdad6a1..7b9fcfec 100644 --- a/api/tokens.go +++ b/api/tokens.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "math/big" + "strconv" "strings" "github.com/ethereum/go-ethereum/common" @@ -31,6 +32,10 @@ func (capi *census3API) initTokenHandlers() error { api.MethodAccessTypePublic, capi.getToken); err != nil { return err } + if err := capi.endpoint.RegisterMethod("/tokens/{tokenID}/{chainID}/holders/{holderID}", "GET", + api.MethodAccessTypePublic, capi.isTokenHolder); err != nil { + return err + } return capi.endpoint.RegisterMethod("/tokens/types", "GET", api.MethodAccessTypePublic, capi.getTokenTypes) } @@ -271,6 +276,27 @@ func (capi *census3API) getToken(msg *api.APIdata, ctx *httprouter.HTTPContext) return ctx.Send(res, api.HTTPstatusOK) } +func (capi *census3API) isTokenHolder(msg *api.APIdata, ctx *httprouter.HTTPContext) error { + address := common.HexToAddress(ctx.URLParam("tokenID")) + holderID := common.HexToAddress(ctx.URLParam("holderID")) + chainID, err := strconv.Atoi(ctx.URLParam("chainID")) + if err != nil { + return ErrMalformedChainID.WithErr(err) + } + internalCtx, cancel := context.WithTimeout(context.Background(), getTokenTimeout) + defer cancel() + + exists, err := capi.db.QueriesRO.ExistTokenHolder(internalCtx, queries.ExistTokenHolderParams{ + TokenID: address.Bytes(), + HolderID: holderID.Bytes(), + ChainID: uint64(chainID), + }) + if err != nil { + return ErrCantGetTokenHolders.WithErr(err) + } + return ctx.Send([]byte(strconv.FormatBool(exists)), api.HTTPstatusOK) +} + // getTokenTypes handler returns the list of string names of the currently // supported types of token contracts. func (capi *census3API) getTokenTypes(msg *api.APIdata, ctx *httprouter.HTTPContext) error { diff --git a/census/census.go b/census/census.go index 15cd49ca..759de18b 100644 --- a/census/census.go +++ b/census/census.go @@ -249,7 +249,7 @@ func censusDBKey(censusID uint64) string { // represented as 1 for true and 0 for false. This concatenated string is then converted // to a uint64 to create a unique identifier. func InnerCensusID(blockNumber, strategyID uint64, anonymous bool) uint64 { - // Convert the boolean to a uint32: 1 for true, 0 for false + // Convert the boolean to a uint64: 1 for true, 0 for false var anonymousUint uint64 if anonymous { anonymousUint = 1 diff --git a/db/queries/holders.sql b/db/queries/holders.sql index 2d6f0ca5..d49fea1f 100644 --- a/db/queries/holders.sql +++ b/db/queries/holders.sql @@ -22,6 +22,15 @@ WHERE token_holders.token_id = ? AND token_holders.chain_id = ? AND token_holders.holder_id = ?; +-- name: ExistTokenHolder :one +SELECT EXISTS ( + SELECT holder_id + FROM token_holders + WHERE token_id = ? + AND holder_id = ? + AND chain_id = ? +); + -- name: LastBlockByTokenID :one SELECT block_id FROM token_holders diff --git a/db/sqlc/holders.sql.go b/db/sqlc/holders.sql.go index 85761e6b..858b9744 100644 --- a/db/sqlc/holders.sql.go +++ b/db/sqlc/holders.sql.go @@ -144,6 +144,29 @@ func (q *Queries) DeleteTokenHolder(ctx context.Context, arg DeleteTokenHolderPa return q.db.ExecContext(ctx, deleteTokenHolder, arg.TokenID, arg.HolderID) } +const existTokenHolder = `-- name: ExistTokenHolder :one +SELECT EXISTS ( + SELECT holder_id + FROM token_holders + WHERE token_id = ? + AND holder_id = ? + AND chain_id = ? +) +` + +type ExistTokenHolderParams struct { + TokenID []byte + HolderID []byte + ChainID uint64 +} + +func (q *Queries) ExistTokenHolder(ctx context.Context, arg ExistTokenHolderParams) (bool, error) { + row := q.db.QueryRowContext(ctx, existTokenHolder, arg.TokenID, arg.HolderID, arg.ChainID) + var exists bool + err := row.Scan(&exists) + return exists, err +} + const lastBlockByTokenID = `-- name: LastBlockByTokenID :one SELECT block_id FROM token_holders