285 lines
9.0 KiB
Go
285 lines
9.0 KiB
Go
package communities
|
|
|
|
import (
|
|
"context"
|
|
"math/big"
|
|
"strconv"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
"github.com/status-im/status-go/services/wallet/bigint"
|
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
|
)
|
|
|
|
type PermissionedBalance struct {
|
|
Type protobuf.CommunityTokenType `json:"type"`
|
|
Symbol string `json:"symbol"`
|
|
Name string `json:"name"`
|
|
Amount *bigint.BigInt `json:"amount"`
|
|
Decimals uint64 `json:"decimals"`
|
|
}
|
|
|
|
func calculatePermissionedBalancesERC20(
|
|
accountAddresses []gethcommon.Address,
|
|
balances BalancesByChain,
|
|
tokenPermissions []*CommunityTokenPermission,
|
|
) map[gethcommon.Address]map[string]*PermissionedBalance {
|
|
res := make(map[gethcommon.Address]map[string]*PermissionedBalance)
|
|
|
|
// Set with composite key (chain ID + wallet address + contract address) to
|
|
// store if we already processed the balance.
|
|
usedBalances := make(map[string]bool)
|
|
|
|
for _, permission := range tokenPermissions {
|
|
for _, criteria := range permission.TokenCriteria {
|
|
if criteria.Type != protobuf.CommunityTokenType_ERC20 {
|
|
continue
|
|
}
|
|
|
|
for _, accountAddress := range accountAddresses {
|
|
for chainID, hexContractAddress := range criteria.ContractAddresses {
|
|
usedKey := strconv.FormatUint(chainID, 10) + "-" + accountAddress.Hex() + "-" + hexContractAddress
|
|
|
|
if _, ok := balances[chainID]; !ok {
|
|
continue
|
|
}
|
|
if _, ok := balances[chainID][accountAddress]; !ok {
|
|
continue
|
|
}
|
|
|
|
contractAddress := gethcommon.HexToAddress(hexContractAddress)
|
|
value, ok := balances[chainID][accountAddress][contractAddress]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Skip the contract address if it has been used already in the sum.
|
|
if _, ok := usedBalances[usedKey]; ok {
|
|
continue
|
|
}
|
|
|
|
if _, ok := res[accountAddress]; !ok {
|
|
res[accountAddress] = make(map[string]*PermissionedBalance, 0)
|
|
}
|
|
if _, ok := res[accountAddress][criteria.Symbol]; !ok {
|
|
res[accountAddress][criteria.Symbol] = &PermissionedBalance{
|
|
Type: criteria.Type,
|
|
Symbol: criteria.Symbol,
|
|
Name: criteria.Name,
|
|
Decimals: criteria.Decimals,
|
|
Amount: &bigint.BigInt{Int: big.NewInt(0)},
|
|
}
|
|
}
|
|
|
|
res[accountAddress][criteria.Symbol].Amount.Add(
|
|
res[accountAddress][criteria.Symbol].Amount.Int,
|
|
value.ToInt(),
|
|
)
|
|
usedBalances[usedKey] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func isERC721CriteriaSatisfied(tokenBalances []thirdparty.TokenBalance, criteria *protobuf.TokenCriteria) bool {
|
|
// No token IDs to compare against, so the criteria is satisfied.
|
|
if len(criteria.TokenIds) == 0 {
|
|
return true
|
|
}
|
|
|
|
for _, tokenID := range criteria.TokenIds {
|
|
tokenIDBigInt := new(big.Int).SetUint64(tokenID)
|
|
for _, asset := range tokenBalances {
|
|
if asset.TokenID.Cmp(tokenIDBigInt) == 0 && asset.Balance.Sign() > 0 {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (m *Manager) calculatePermissionedBalancesERC721(
|
|
accountAddresses []gethcommon.Address,
|
|
balances CollectiblesByChain,
|
|
tokenPermissions []*CommunityTokenPermission,
|
|
) map[gethcommon.Address]map[string]*PermissionedBalance {
|
|
res := make(map[gethcommon.Address]map[string]*PermissionedBalance)
|
|
|
|
// Set with composite key (chain ID + wallet address + contract address) to
|
|
// store if we already processed the balance.
|
|
usedBalances := make(map[string]bool)
|
|
|
|
for _, permission := range tokenPermissions {
|
|
for _, criteria := range permission.TokenCriteria {
|
|
if criteria.Type != protobuf.CommunityTokenType_ERC721 {
|
|
continue
|
|
}
|
|
|
|
for _, accountAddress := range accountAddresses {
|
|
for chainID, hexContractAddress := range criteria.ContractAddresses {
|
|
usedKey := strconv.FormatUint(chainID, 10) + "-" + accountAddress.Hex() + "-" + hexContractAddress
|
|
|
|
if _, ok := balances[chainID]; !ok {
|
|
continue
|
|
}
|
|
if _, ok := balances[chainID][accountAddress]; !ok {
|
|
continue
|
|
}
|
|
|
|
contractAddress := gethcommon.HexToAddress(hexContractAddress)
|
|
tokenBalances, ok := balances[chainID][accountAddress][contractAddress]
|
|
if !ok || len(tokenBalances) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Skip the contract address if it has been used already in the sum.
|
|
if _, ok := usedBalances[usedKey]; ok {
|
|
continue
|
|
}
|
|
|
|
usedBalances[usedKey] = true
|
|
|
|
if _, ok := res[accountAddress]; !ok {
|
|
res[accountAddress] = make(map[string]*PermissionedBalance, 0)
|
|
}
|
|
if _, ok := res[accountAddress][criteria.Symbol]; !ok {
|
|
res[accountAddress][criteria.Symbol] = &PermissionedBalance{
|
|
Type: criteria.Type,
|
|
Symbol: criteria.Symbol,
|
|
Name: criteria.Name,
|
|
Decimals: criteria.Decimals,
|
|
Amount: &bigint.BigInt{Int: big.NewInt(0)},
|
|
}
|
|
}
|
|
|
|
if isERC721CriteriaSatisfied(tokenBalances, criteria) {
|
|
// We don't care about summing balances, thus setting as 1 is
|
|
// sufficient.
|
|
res[accountAddress][criteria.Symbol].Amount = &bigint.BigInt{Int: big.NewInt(1)}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func (m *Manager) calculatePermissionedBalances(
|
|
chainIDs []uint64,
|
|
accountAddresses []gethcommon.Address,
|
|
erc20Balances BalancesByChain,
|
|
erc721Balances CollectiblesByChain,
|
|
tokenPermissions []*CommunityTokenPermission,
|
|
) map[gethcommon.Address][]PermissionedBalance {
|
|
res := make(map[gethcommon.Address][]PermissionedBalance, 0)
|
|
|
|
aggregatedERC721Balances := m.calculatePermissionedBalancesERC721(accountAddresses, erc721Balances, tokenPermissions)
|
|
for accountAddress, tokens := range aggregatedERC721Balances {
|
|
for _, permissionedToken := range tokens {
|
|
if permissionedToken.Amount.Sign() > 0 {
|
|
res[accountAddress] = append(res[accountAddress], *permissionedToken)
|
|
}
|
|
}
|
|
}
|
|
|
|
aggregatedERC20Balances := calculatePermissionedBalancesERC20(accountAddresses, erc20Balances, tokenPermissions)
|
|
for accountAddress, tokens := range aggregatedERC20Balances {
|
|
for _, permissionedToken := range tokens {
|
|
if permissionedToken.Amount.Sign() > 0 {
|
|
res[accountAddress] = append(res[accountAddress], *permissionedToken)
|
|
}
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func keepRoleTokenPermissions(tokenPermissions map[string]*CommunityTokenPermission) []*CommunityTokenPermission {
|
|
res := make([]*CommunityTokenPermission, 0)
|
|
for _, p := range tokenPermissions {
|
|
if p.Type == protobuf.CommunityTokenPermission_BECOME_MEMBER ||
|
|
p.Type == protobuf.CommunityTokenPermission_BECOME_ADMIN ||
|
|
p.Type == protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER ||
|
|
p.Type == protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER {
|
|
res = append(res, p)
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
// GetPermissionedBalances returns balances indexed by account address.
|
|
//
|
|
// It assumes balances in different chains with the same symbol can be summed.
|
|
// It also assumes the criteria's decimals field is the same across different
|
|
// criteria when they refer to the same asset (by symbol).
|
|
func (m *Manager) GetPermissionedBalances(
|
|
ctx context.Context,
|
|
communityID types.HexBytes,
|
|
accountAddresses []gethcommon.Address,
|
|
) (map[gethcommon.Address][]PermissionedBalance, error) {
|
|
community, err := m.GetByID(communityID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if community == nil {
|
|
return nil, errors.Errorf("community does not exist ID='%s'", communityID)
|
|
}
|
|
|
|
tokenPermissions := keepRoleTokenPermissions(community.TokenPermissions())
|
|
|
|
allChainIDs, err := m.tokenManager.GetAllChainIDs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
accountsAndChainIDs := combineAddressesAndChainIDs(accountAddresses, allChainIDs)
|
|
|
|
erc20TokenCriteriaByChain, erc721TokenCriteriaByChain, _ := ExtractTokenCriteria(tokenPermissions)
|
|
|
|
accounts := make([]gethcommon.Address, 0, len(accountsAndChainIDs))
|
|
for _, accountAndChainIDs := range accountsAndChainIDs {
|
|
accounts = append(accounts, accountAndChainIDs.Address)
|
|
}
|
|
|
|
erc20ChainIDsSet := make(map[uint64]bool)
|
|
erc20TokenAddresses := make([]gethcommon.Address, 0)
|
|
for chainID, criterionByContractAddress := range erc20TokenCriteriaByChain {
|
|
erc20ChainIDsSet[chainID] = true
|
|
for contractAddress := range criterionByContractAddress {
|
|
erc20TokenAddresses = append(erc20TokenAddresses, gethcommon.HexToAddress(contractAddress))
|
|
}
|
|
}
|
|
|
|
erc721ChainIDsSet := make(map[uint64]bool)
|
|
for chainID := range erc721TokenCriteriaByChain {
|
|
erc721ChainIDsSet[chainID] = true
|
|
}
|
|
|
|
erc20ChainIDs := calculateChainIDsSet(accountsAndChainIDs, erc20ChainIDsSet)
|
|
erc721ChainIDs := calculateChainIDsSet(accountsAndChainIDs, erc721ChainIDsSet)
|
|
|
|
erc20Balances, err := m.tokenManager.GetBalancesByChain(ctx, accounts, erc20TokenAddresses, erc20ChainIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
erc721Balances := make(CollectiblesByChain)
|
|
if len(erc721ChainIDs) > 0 {
|
|
balances, err := m.GetOwnedERC721Tokens(accounts, erc721TokenCriteriaByChain, erc721ChainIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
erc721Balances = balances
|
|
}
|
|
|
|
return m.calculatePermissionedBalances(allChainIDs, accountAddresses, erc20Balances, erc721Balances, tokenPermissions), nil
|
|
}
|