github.com/status-im/status-go@v1.1.0/services/communitytokens/manager.go (about)

     1  package communitytokens
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
     8  	"github.com/ethereum/go-ethereum/common"
     9  	"github.com/ethereum/go-ethereum/common/math"
    10  	"github.com/ethereum/go-ethereum/signer/core/apitypes"
    11  	"github.com/status-im/status-go/contracts/community-tokens/assets"
    12  	"github.com/status-im/status-go/contracts/community-tokens/collectibles"
    13  	communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer"
    14  	"github.com/status-im/status-go/eth-node/crypto"
    15  	"github.com/status-im/status-go/eth-node/types"
    16  	"github.com/status-im/status-go/protocol/communities"
    17  	"github.com/status-im/status-go/rpc"
    18  	"github.com/status-im/status-go/services/wallet/bigint"
    19  )
    20  
    21  type Manager struct {
    22  	rpcClient *rpc.Client
    23  }
    24  
    25  func NewManager(rpcClient *rpc.Client) *Manager {
    26  	return &Manager{
    27  		rpcClient: rpcClient,
    28  	}
    29  }
    30  
    31  func (m *Manager) NewCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
    32  	backend, err := m.rpcClient.EthClient(chainID)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  	return collectibles.NewCollectibles(common.HexToAddress(contractAddress), backend)
    37  }
    38  
    39  func (m *Manager) NewCommunityTokenDeployerInstance(chainID uint64) (*communitytokendeployer.CommunityTokenDeployer, error) {
    40  	backend, err := m.rpcClient.EthClient(chainID)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	deployerAddr, err := communitytokendeployer.ContractAddress(chainID)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	return communitytokendeployer.NewCommunityTokenDeployer(deployerAddr, backend)
    49  }
    50  
    51  func (m *Manager) GetCollectiblesContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
    52  	contractInst, err := m.NewCollectiblesInstance(chainID, contractAddress)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	return contractInst, nil
    57  }
    58  
    59  func (m *Manager) NewAssetsInstance(chainID uint64, contractAddress string) (*assets.Assets, error) {
    60  	backend, err := m.rpcClient.EthClient(chainID)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	return assets.NewAssets(common.HexToAddress(contractAddress), backend)
    65  }
    66  
    67  func (m *Manager) GetAssetContractInstance(chainID uint64, contractAddress string) (*assets.Assets, error) {
    68  	contractInst, err := m.NewAssetsInstance(chainID, contractAddress)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	return contractInst, nil
    73  }
    74  
    75  func (m *Manager) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) {
    76  	callOpts := &bind.CallOpts{Context: context.Background(), Pending: false}
    77  
    78  	contract, err := m.GetCollectiblesContractInstance(chainID, contractAddress)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	totalSupply, err := contract.MaxSupply(callOpts)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	transferable, err := contract.Transferable(callOpts)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	remoteBurnable, err := contract.RemoteBurnable(callOpts)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	return &communities.CollectibleContractData{
    96  		TotalSupply:    &bigint.BigInt{Int: totalSupply},
    97  		Transferable:   transferable,
    98  		RemoteBurnable: remoteBurnable,
    99  		InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0,
   100  	}, nil
   101  }
   102  
   103  func (m *Manager) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) {
   104  	callOpts := &bind.CallOpts{Context: context.Background(), Pending: false}
   105  	contract, err := m.GetAssetContractInstance(chainID, contractAddress)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	totalSupply, err := contract.MaxSupply(callOpts)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	return &communities.AssetContractData{
   115  		TotalSupply:    &bigint.BigInt{Int: totalSupply},
   116  		InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0,
   117  	}, nil
   118  }
   119  
   120  func convert33BytesPubKeyToEthAddress(pubKey string) (common.Address, error) {
   121  	decoded, err := types.DecodeHex(pubKey)
   122  	if err != nil {
   123  		return common.Address{}, err
   124  	}
   125  	communityPubKey, err := crypto.DecompressPubkey(decoded)
   126  	if err != nil {
   127  		return common.Address{}, err
   128  	}
   129  	return common.Address(crypto.PubkeyToAddress(*communityPubKey)), nil
   130  }
   131  
   132  // Simpler version of hashing typed structured data alternative to typedStructuredDataHash. Keeping this for reference.
   133  func customTypedStructuredDataHash(domainSeparator []byte, signatureTypedHash []byte, signer string, deployer string) types.Hash {
   134  	// every field should be 32 bytes, eth address is 20 bytes so padding should be added
   135  	emptyOffset := [12]byte{}
   136  	hashedEncoded := crypto.Keccak256Hash(signatureTypedHash, emptyOffset[:], common.HexToAddress(signer).Bytes(),
   137  		emptyOffset[:], common.HexToAddress(deployer).Bytes())
   138  	rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, hashedEncoded.Bytes()))
   139  	return crypto.Keccak256Hash(rawData)
   140  }
   141  
   142  // Returns a typed structured hash according to https://eips.ethereum.org/EIPS/eip-712
   143  // Domain separator from smart contract is used.
   144  func typedStructuredDataHash(domainSeparator []byte, signer string, addressFrom string, deployerContractAddress string, chainID uint64) (types.Hash, error) {
   145  	myTypedData := apitypes.TypedData{
   146  		Types: apitypes.Types{
   147  			"Deploy": []apitypes.Type{
   148  				{Name: "signer", Type: "address"},
   149  				{Name: "deployer", Type: "address"},
   150  			},
   151  			"EIP712Domain": []apitypes.Type{
   152  				{Name: "name", Type: "string"},
   153  				{Name: "version", Type: "string"},
   154  				{Name: "chainId", Type: "uint256"},
   155  				{Name: "verifyingContract", Type: "address"},
   156  			},
   157  		},
   158  		PrimaryType: "Deploy",
   159  		// Domain field should be here to keep correct structure but
   160  		// domainSeparator from smart contract is used.
   161  		Domain: apitypes.TypedDataDomain{
   162  			Name:              "CommunityTokenDeployer", // name from Deployer smart contract
   163  			Version:           "1",                      // version from Deployer smart contract
   164  			ChainId:           math.NewHexOrDecimal256(int64(chainID)),
   165  			VerifyingContract: deployerContractAddress,
   166  		},
   167  		Message: apitypes.TypedDataMessage{
   168  			"signer":   signer,
   169  			"deployer": addressFrom,
   170  		},
   171  	}
   172  
   173  	typedDataHash, err := myTypedData.HashStruct(myTypedData.PrimaryType, myTypedData.Message)
   174  	if err != nil {
   175  		return types.Hash{}, err
   176  	}
   177  	rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, string(typedDataHash)))
   178  	return crypto.Keccak256Hash(rawData), nil
   179  }
   180  
   181  // Creates
   182  func (m *Manager) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) {
   183  	callOpts := &bind.CallOpts{Pending: false}
   184  	communityEthAddr, err := convert33BytesPubKeyToEthAddress(communityID)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	deployerAddr, err := communitytokendeployer.ContractAddress(chainID)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	deployerContractInst, err := m.NewCommunityTokenDeployerInstance(chainID)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	domainSeparator, err := deployerContractInst.DOMAINSEPARATOR(callOpts)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	structedHash, err := typedStructuredDataHash(domainSeparator[:], communityEthAddr.Hex(), addressFrom, deployerAddr.Hex(), chainID)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	return structedHash.Bytes(), nil
   209  }