mirror of
https://github.com/status-im/status-go.git
synced 2025-01-20 11:40:29 +00:00
23f71c1125
This commit changes the format of the encryption id to be based off 3 things: 1) The group id 2) The timestamp 3) The actual key Previously this was solely based on the timestamp and the group id, but this might lead to conflicts. Moreover the format of the key was an uint32 and so it would wrap periodically. The migration is a bit tricky, so first we cleared the cache of keys, that's easier than migrating, and second we set the new field hash_id to the concatenation of group_id / key_id. This might lead on some duplication in case keys are re-received, but it should not have an impact on the correctness of the code. I have added 2 tests covering compatibility between old/new clients, as this should not be a breaking change. It also adds a new message to rekey in a single go, instead of having to send multiple messages
356 lines
12 KiB
Go
356 lines
12 KiB
Go
package communities
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math"
|
|
"math/big"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/status-im/status-go/protocol/ens"
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
walletcommon "github.com/status-im/status-go/services/wallet/common"
|
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
|
)
|
|
|
|
type PermissionChecker interface {
|
|
CheckPermissionToJoin(*Community, []gethcommon.Address) (*CheckPermissionToJoinResponse, error)
|
|
CheckPermissions(permissions []*CommunityTokenPermission, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionsResponse, error)
|
|
}
|
|
|
|
type DefaultPermissionChecker struct {
|
|
tokenManager TokenManager
|
|
collectiblesManager CollectiblesManager
|
|
ensVerifier *ens.Verifier
|
|
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func (p *DefaultPermissionChecker) getOwnedENS(addresses []gethcommon.Address) ([]string, error) {
|
|
ownedENS := make([]string, 0)
|
|
if p.ensVerifier == nil {
|
|
p.logger.Warn("no ensVerifier configured for communities manager")
|
|
return ownedENS, nil
|
|
}
|
|
for _, address := range addresses {
|
|
name, err := p.ensVerifier.ReverseResolve(address)
|
|
if err != nil && err.Error() != "not a resolver" {
|
|
return ownedENS, err
|
|
}
|
|
if name != "" {
|
|
ownedENS = append(ownedENS, name)
|
|
}
|
|
}
|
|
return ownedENS, nil
|
|
}
|
|
func (p *DefaultPermissionChecker) GetOwnedERC721Tokens(walletAddresses []gethcommon.Address, tokenRequirements map[uint64]map[string]*protobuf.TokenCriteria, chainIDs []uint64) (CollectiblesByChain, error) {
|
|
if p.collectiblesManager == nil {
|
|
return nil, errors.New("no collectibles manager")
|
|
}
|
|
|
|
ownedERC721Tokens := make(CollectiblesByChain)
|
|
|
|
for chainID, erc721Tokens := range tokenRequirements {
|
|
|
|
skipChain := true
|
|
for _, cID := range chainIDs {
|
|
if chainID == cID {
|
|
skipChain = false
|
|
}
|
|
}
|
|
|
|
if skipChain {
|
|
continue
|
|
}
|
|
|
|
contractAddresses := make([]gethcommon.Address, 0)
|
|
for contractAddress := range erc721Tokens {
|
|
contractAddresses = append(contractAddresses, gethcommon.HexToAddress(contractAddress))
|
|
}
|
|
|
|
if _, exists := ownedERC721Tokens[chainID]; !exists {
|
|
ownedERC721Tokens[chainID] = make(map[gethcommon.Address]thirdparty.TokenBalancesPerContractAddress)
|
|
}
|
|
|
|
for _, owner := range walletAddresses {
|
|
balances, err := p.collectiblesManager.FetchBalancesByOwnerAndContractAddress(walletcommon.ChainID(chainID), owner, contractAddresses)
|
|
if err != nil {
|
|
p.logger.Info("couldn't fetch owner assets", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
ownedERC721Tokens[chainID][owner] = balances
|
|
}
|
|
}
|
|
return ownedERC721Tokens, nil
|
|
}
|
|
|
|
func (p *DefaultPermissionChecker) CheckPermissionToJoin(community *Community, addresses []gethcommon.Address) (*CheckPermissionToJoinResponse, error) {
|
|
becomeAdminPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_ADMIN)
|
|
becomeMemberPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER)
|
|
becomeTokenMasterPermissions := community.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER)
|
|
|
|
permissionsToJoin := append(becomeAdminPermissions, becomeMemberPermissions...)
|
|
permissionsToJoin = append(permissionsToJoin, becomeTokenMasterPermissions...)
|
|
|
|
allChainIDs, err := p.tokenManager.GetAllChainIDs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
accountsAndChainIDs := combineAddressesAndChainIDs(addresses, allChainIDs)
|
|
|
|
if len(becomeMemberPermissions) == 0 || len(permissionsToJoin) == 0 {
|
|
// There are no permissions to join on this community at the moment,
|
|
// so we reveal all accounts + all chain IDs
|
|
response := &CheckPermissionsResponse{
|
|
Satisfied: true,
|
|
Permissions: make(map[string]*PermissionTokenCriteriaResult),
|
|
ValidCombinations: accountsAndChainIDs,
|
|
}
|
|
return response, nil
|
|
}
|
|
return p.CheckPermissions(permissionsToJoin, accountsAndChainIDs, false)
|
|
}
|
|
|
|
// CheckPermissions will retrieve balances and check whether the user has
|
|
// permission to join the community, if shortcircuit is true, it will stop as soon
|
|
// as we know the answer
|
|
func (p *DefaultPermissionChecker) CheckPermissions(permissions []*CommunityTokenPermission, accountsAndChainIDs []*AccountChainIDsCombination, shortcircuit bool) (*CheckPermissionsResponse, error) {
|
|
|
|
response := &CheckPermissionsResponse{
|
|
Satisfied: false,
|
|
Permissions: make(map[string]*PermissionTokenCriteriaResult),
|
|
ValidCombinations: make([]*AccountChainIDsCombination, 0),
|
|
}
|
|
|
|
erc20TokenRequirements, erc721TokenRequirements, _ := ExtractTokenCriteria(permissions)
|
|
|
|
erc20ChainIDsMap := make(map[uint64]bool)
|
|
erc721ChainIDsMap := make(map[uint64]bool)
|
|
|
|
erc20TokenAddresses := make([]gethcommon.Address, 0)
|
|
accounts := make([]gethcommon.Address, 0)
|
|
|
|
for _, accountAndChainIDs := range accountsAndChainIDs {
|
|
accounts = append(accounts, accountAndChainIDs.Address)
|
|
}
|
|
|
|
// figure out chain IDs we're interested in
|
|
for chainID, tokens := range erc20TokenRequirements {
|
|
erc20ChainIDsMap[chainID] = true
|
|
for contractAddress := range tokens {
|
|
erc20TokenAddresses = append(erc20TokenAddresses, gethcommon.HexToAddress(contractAddress))
|
|
}
|
|
}
|
|
|
|
for chainID := range erc721TokenRequirements {
|
|
erc721ChainIDsMap[chainID] = true
|
|
}
|
|
|
|
chainIDsForERC20 := calculateChainIDsSet(accountsAndChainIDs, erc20ChainIDsMap)
|
|
chainIDsForERC721 := calculateChainIDsSet(accountsAndChainIDs, erc721ChainIDsMap)
|
|
|
|
// if there are no chain IDs that match token criteria chain IDs
|
|
// we aren't able to check balances on selected networks
|
|
if len(erc20ChainIDsMap) > 0 && len(chainIDsForERC20) == 0 {
|
|
return response, nil
|
|
}
|
|
|
|
ownedERC20TokenBalances := make(map[uint64]map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big, 0)
|
|
if len(chainIDsForERC20) > 0 {
|
|
// this only returns balances for the networks we're actually interested in
|
|
balances, err := p.tokenManager.GetBalancesByChain(context.Background(), accounts, erc20TokenAddresses, chainIDsForERC20)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ownedERC20TokenBalances = balances
|
|
}
|
|
|
|
ownedERC721Tokens := make(CollectiblesByChain)
|
|
if len(chainIDsForERC721) > 0 {
|
|
collectibles, err := p.GetOwnedERC721Tokens(accounts, erc721TokenRequirements, chainIDsForERC721)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ownedERC721Tokens = collectibles
|
|
}
|
|
|
|
accountsChainIDsCombinations := make(map[gethcommon.Address]map[uint64]bool)
|
|
|
|
for _, tokenPermission := range permissions {
|
|
|
|
permissionRequirementsMet := true
|
|
response.Permissions[tokenPermission.Id] = &PermissionTokenCriteriaResult{}
|
|
|
|
// There can be multiple token requirements per permission.
|
|
// If only one is not met, the entire permission is marked
|
|
// as not fulfilled
|
|
for _, tokenRequirement := range tokenPermission.TokenCriteria {
|
|
|
|
tokenRequirementMet := false
|
|
|
|
if tokenRequirement.Type == protobuf.CommunityTokenType_ERC721 {
|
|
if len(ownedERC721Tokens) == 0 {
|
|
response.Permissions[tokenPermission.Id].Criteria = append(response.Permissions[tokenPermission.Id].Criteria, false)
|
|
continue
|
|
}
|
|
|
|
chainIDLoopERC721:
|
|
for chainID, addressStr := range tokenRequirement.ContractAddresses {
|
|
contractAddress := gethcommon.HexToAddress(addressStr)
|
|
if _, exists := ownedERC721Tokens[chainID]; !exists || len(ownedERC721Tokens[chainID]) == 0 {
|
|
continue chainIDLoopERC721
|
|
}
|
|
|
|
for account := range ownedERC721Tokens[chainID] {
|
|
if _, exists := ownedERC721Tokens[chainID][account]; !exists {
|
|
continue
|
|
}
|
|
|
|
tokenBalances := ownedERC721Tokens[chainID][account][contractAddress]
|
|
if len(tokenBalances) > 0 {
|
|
// 'account' owns some TokenID owned from contract 'address'
|
|
if _, exists := accountsChainIDsCombinations[account]; !exists {
|
|
accountsChainIDsCombinations[account] = make(map[uint64]bool)
|
|
}
|
|
|
|
if len(tokenRequirement.TokenIds) == 0 {
|
|
// no specific tokenId of this collection is needed
|
|
tokenRequirementMet = true
|
|
accountsChainIDsCombinations[account][chainID] = true
|
|
break chainIDLoopERC721
|
|
}
|
|
|
|
tokenIDsLoop:
|
|
for _, tokenID := range tokenRequirement.TokenIds {
|
|
tokenIDBigInt := new(big.Int).SetUint64(tokenID)
|
|
|
|
for _, asset := range tokenBalances {
|
|
if asset.TokenID.Cmp(tokenIDBigInt) == 0 && asset.Balance.Sign() > 0 {
|
|
tokenRequirementMet = true
|
|
accountsChainIDsCombinations[account][chainID] = true
|
|
break tokenIDsLoop
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if tokenRequirement.Type == protobuf.CommunityTokenType_ERC20 {
|
|
if len(ownedERC20TokenBalances) == 0 {
|
|
response.Permissions[tokenPermission.Id].Criteria = append(response.Permissions[tokenPermission.Id].Criteria, false)
|
|
continue
|
|
}
|
|
|
|
accumulatedBalance := new(big.Float)
|
|
|
|
chainIDLoopERC20:
|
|
for chainID, address := range tokenRequirement.ContractAddresses {
|
|
if _, exists := ownedERC20TokenBalances[chainID]; !exists || len(ownedERC20TokenBalances[chainID]) == 0 {
|
|
continue chainIDLoopERC20
|
|
}
|
|
contractAddress := gethcommon.HexToAddress(address)
|
|
for account := range ownedERC20TokenBalances[chainID] {
|
|
if _, exists := ownedERC20TokenBalances[chainID][account][contractAddress]; !exists {
|
|
continue
|
|
}
|
|
|
|
value := ownedERC20TokenBalances[chainID][account][contractAddress]
|
|
|
|
accountChainBalance := new(big.Float).Quo(
|
|
new(big.Float).SetInt(value.ToInt()),
|
|
big.NewFloat(math.Pow(10, float64(tokenRequirement.Decimals))),
|
|
)
|
|
|
|
if _, exists := accountsChainIDsCombinations[account]; !exists {
|
|
accountsChainIDsCombinations[account] = make(map[uint64]bool)
|
|
}
|
|
|
|
if accountChainBalance.Cmp(big.NewFloat(0)) > 0 {
|
|
// account has balance > 0 on this chain for this token, so let's add it the chain IDs
|
|
accountsChainIDsCombinations[account][chainID] = true
|
|
}
|
|
|
|
// check if adding current chain account balance to accumulated balance
|
|
// satisfies required amount
|
|
prevBalance := accumulatedBalance
|
|
accumulatedBalance.Add(prevBalance, accountChainBalance)
|
|
|
|
requiredAmount, err := strconv.ParseFloat(tokenRequirement.Amount, 32)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if accumulatedBalance.Cmp(big.NewFloat(requiredAmount)) != -1 {
|
|
tokenRequirementMet = true
|
|
if shortcircuit {
|
|
break chainIDLoopERC20
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if tokenRequirement.Type == protobuf.CommunityTokenType_ENS {
|
|
|
|
for _, account := range accounts {
|
|
ownedENSNames, err := p.getOwnedENS([]gethcommon.Address{account})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, exists := accountsChainIDsCombinations[account]; !exists {
|
|
accountsChainIDsCombinations[account] = make(map[uint64]bool)
|
|
}
|
|
|
|
if !strings.HasPrefix(tokenRequirement.EnsPattern, "*.") {
|
|
for _, ownedENS := range ownedENSNames {
|
|
if ownedENS == tokenRequirement.EnsPattern {
|
|
tokenRequirementMet = true
|
|
accountsChainIDsCombinations[account][walletcommon.EthereumMainnet] = true
|
|
}
|
|
}
|
|
} else {
|
|
parentName := tokenRequirement.EnsPattern[2:]
|
|
for _, ownedENS := range ownedENSNames {
|
|
if strings.HasSuffix(ownedENS, parentName) {
|
|
tokenRequirementMet = true
|
|
accountsChainIDsCombinations[account][walletcommon.EthereumMainnet] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !tokenRequirementMet {
|
|
permissionRequirementsMet = false
|
|
}
|
|
response.Permissions[tokenPermission.Id].Criteria = append(response.Permissions[tokenPermission.Id].Criteria, tokenRequirementMet)
|
|
}
|
|
// multiple permissions are treated as logical OR, meaning
|
|
// if only one of them is fulfilled, the user gets permission
|
|
// to join and we can stop early
|
|
if shortcircuit && permissionRequirementsMet {
|
|
break
|
|
}
|
|
}
|
|
|
|
// attach valid account and chainID combinations to response
|
|
for account, chainIDs := range accountsChainIDsCombinations {
|
|
combination := &AccountChainIDsCombination{
|
|
Address: account,
|
|
}
|
|
for chainID := range chainIDs {
|
|
combination.ChainIDs = append(combination.ChainIDs, chainID)
|
|
}
|
|
response.ValidCombinations = append(response.ValidCombinations, combination)
|
|
}
|
|
|
|
response.calculateSatisfied()
|
|
|
|
return response, nil
|
|
}
|