210 lines
6.9 KiB
Go
210 lines
6.9 KiB
Go
package communitytokens
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/math"
|
|
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
|
"github.com/status-im/status-go/contracts/community-tokens/assets"
|
|
"github.com/status-im/status-go/contracts/community-tokens/collectibles"
|
|
communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer"
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
"github.com/status-im/status-go/protocol/communities"
|
|
"github.com/status-im/status-go/rpc"
|
|
"github.com/status-im/status-go/services/wallet/bigint"
|
|
)
|
|
|
|
type Manager struct {
|
|
rpcClient *rpc.Client
|
|
}
|
|
|
|
func NewManager(rpcClient *rpc.Client) *Manager {
|
|
return &Manager{
|
|
rpcClient: rpcClient,
|
|
}
|
|
}
|
|
|
|
func (m *Manager) NewCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
|
|
backend, err := m.rpcClient.EthClient(chainID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return collectibles.NewCollectibles(common.HexToAddress(contractAddress), backend)
|
|
}
|
|
|
|
func (m *Manager) NewCommunityTokenDeployerInstance(chainID uint64) (*communitytokendeployer.CommunityTokenDeployer, error) {
|
|
backend, err := m.rpcClient.EthClient(chainID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
deployerAddr, err := communitytokendeployer.ContractAddress(chainID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return communitytokendeployer.NewCommunityTokenDeployer(deployerAddr, backend)
|
|
}
|
|
|
|
func (m *Manager) GetCollectiblesContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
|
|
contractInst, err := m.NewCollectiblesInstance(chainID, contractAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return contractInst, nil
|
|
}
|
|
|
|
func (m *Manager) NewAssetsInstance(chainID uint64, contractAddress string) (*assets.Assets, error) {
|
|
backend, err := m.rpcClient.EthClient(chainID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return assets.NewAssets(common.HexToAddress(contractAddress), backend)
|
|
}
|
|
|
|
func (m *Manager) GetAssetContractInstance(chainID uint64, contractAddress string) (*assets.Assets, error) {
|
|
contractInst, err := m.NewAssetsInstance(chainID, contractAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return contractInst, nil
|
|
}
|
|
|
|
func (m *Manager) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) {
|
|
callOpts := &bind.CallOpts{Context: context.Background(), Pending: false}
|
|
|
|
contract, err := m.GetCollectiblesContractInstance(chainID, contractAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
totalSupply, err := contract.MaxSupply(callOpts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
transferable, err := contract.Transferable(callOpts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
remoteBurnable, err := contract.RemoteBurnable(callOpts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &communities.CollectibleContractData{
|
|
TotalSupply: &bigint.BigInt{Int: totalSupply},
|
|
Transferable: transferable,
|
|
RemoteBurnable: remoteBurnable,
|
|
InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0,
|
|
}, nil
|
|
}
|
|
|
|
func (m *Manager) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) {
|
|
callOpts := &bind.CallOpts{Context: context.Background(), Pending: false}
|
|
contract, err := m.GetAssetContractInstance(chainID, contractAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
totalSupply, err := contract.MaxSupply(callOpts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &communities.AssetContractData{
|
|
TotalSupply: &bigint.BigInt{Int: totalSupply},
|
|
InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0,
|
|
}, nil
|
|
}
|
|
|
|
func convert33BytesPubKeyToEthAddress(pubKey string) (common.Address, error) {
|
|
decoded, err := types.DecodeHex(pubKey)
|
|
if err != nil {
|
|
return common.Address{}, err
|
|
}
|
|
communityPubKey, err := crypto.DecompressPubkey(decoded)
|
|
if err != nil {
|
|
return common.Address{}, err
|
|
}
|
|
return common.Address(crypto.PubkeyToAddress(*communityPubKey)), nil
|
|
}
|
|
|
|
// Simpler version of hashing typed structured data alternative to typedStructuredDataHash. Keeping this for reference.
|
|
func customTypedStructuredDataHash(domainSeparator []byte, signatureTypedHash []byte, signer string, deployer string) types.Hash {
|
|
// every field should be 32 bytes, eth address is 20 bytes so padding should be added
|
|
emptyOffset := [12]byte{}
|
|
hashedEncoded := crypto.Keccak256Hash(signatureTypedHash, emptyOffset[:], common.HexToAddress(signer).Bytes(),
|
|
emptyOffset[:], common.HexToAddress(deployer).Bytes())
|
|
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, hashedEncoded.Bytes()))
|
|
return crypto.Keccak256Hash(rawData)
|
|
}
|
|
|
|
// Returns a typed structured hash according to https://eips.ethereum.org/EIPS/eip-712
|
|
// Domain separator from smart contract is used.
|
|
func typedStructuredDataHash(domainSeparator []byte, signer string, addressFrom string, deployerContractAddress string, chainID uint64) (types.Hash, error) {
|
|
myTypedData := apitypes.TypedData{
|
|
Types: apitypes.Types{
|
|
"Deploy": []apitypes.Type{
|
|
{Name: "signer", Type: "address"},
|
|
{Name: "deployer", Type: "address"},
|
|
},
|
|
"EIP712Domain": []apitypes.Type{
|
|
{Name: "name", Type: "string"},
|
|
{Name: "version", Type: "string"},
|
|
{Name: "chainId", Type: "uint256"},
|
|
{Name: "verifyingContract", Type: "address"},
|
|
},
|
|
},
|
|
PrimaryType: "Deploy",
|
|
// Domain field should be here to keep correct structure but
|
|
// domainSeparator from smart contract is used.
|
|
Domain: apitypes.TypedDataDomain{
|
|
Name: "CommunityTokenDeployer", // name from Deployer smart contract
|
|
Version: "1", // version from Deployer smart contract
|
|
ChainId: math.NewHexOrDecimal256(int64(chainID)),
|
|
VerifyingContract: deployerContractAddress,
|
|
},
|
|
Message: apitypes.TypedDataMessage{
|
|
"signer": signer,
|
|
"deployer": addressFrom,
|
|
},
|
|
}
|
|
|
|
typedDataHash, err := myTypedData.HashStruct(myTypedData.PrimaryType, myTypedData.Message)
|
|
if err != nil {
|
|
return types.Hash{}, err
|
|
}
|
|
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, string(typedDataHash)))
|
|
return crypto.Keccak256Hash(rawData), nil
|
|
}
|
|
|
|
// Creates
|
|
func (m *Manager) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) {
|
|
callOpts := &bind.CallOpts{Pending: false}
|
|
communityEthAddr, err := convert33BytesPubKeyToEthAddress(communityID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
deployerAddr, err := communitytokendeployer.ContractAddress(chainID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
deployerContractInst, err := m.NewCommunityTokenDeployerInstance(chainID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
domainSeparator, err := deployerContractInst.DOMAINSEPARATOR(callOpts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
structedHash, err := typedStructuredDataHash(domainSeparator[:], communityEthAddr.Hex(), addressFrom, deployerAddr.Hex(), chainID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return structedHash.Bytes(), nil
|
|
}
|