feat(OwnerToken): Owner token and master token deployment

Adding new smart contracts and generated go files.
Deploy token owner function and master token address getter.
Adding deployer and privilegesLevel columns to community_tokens table.
Passing addressFrom to API calls.

Issue #11250
This commit is contained in:
Michal Iskierko 2023-07-18 10:33:45 +02:00 committed by Michał Iskierko
parent b49b9fe3c5
commit 9d0acc2265
22 changed files with 2296 additions and 729 deletions

View File

@ -3,14 +3,14 @@ pragma solidity ^0.8.17;
import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/utils/Context.sol"; import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/utils/Counters.sol";
contract CollectibleV1 is abstract contract BaseToken is
Context, Context,
ERC721Enumerable, ERC721Enumerable
Ownable
{ {
using Counters for Counters.Counter; using Counters for Counters.Counter;
@ -23,6 +23,9 @@ contract CollectibleV1 is
*/ */
uint256 public maxSupply; uint256 public maxSupply;
address public ownerToken;
address public masterToken;
/** /**
* If set to true, the contract owner can burn any token. * If set to true, the contract owner can burn any token.
*/ */
@ -41,19 +44,35 @@ contract CollectibleV1 is
uint256 _maxSupply, uint256 _maxSupply,
bool _remoteBurnable, bool _remoteBurnable,
bool _transferable, bool _transferable,
string memory _baseTokenURI string memory _baseTokenURI,
address _ownerToken,
address _masterToken
) ERC721(_name, _symbol) { ) ERC721(_name, _symbol) {
maxSupply = _maxSupply; maxSupply = _maxSupply;
remoteBurnable = _remoteBurnable; remoteBurnable = _remoteBurnable;
transferable = _transferable; transferable = _transferable;
baseTokenURI = _baseTokenURI; baseTokenURI = _baseTokenURI;
ownerToken = _ownerToken;
masterToken = _masterToken;
require(ownerToken != address(0x0) || masterToken != address(0x0), "owner or master tokens required");
}
modifier onlyOwner() {
require(
(ownerToken == address(0) || IERC721(ownerToken).balanceOf(msg.sender) > 0) ||
(masterToken == address(0) || IERC721(masterToken).balanceOf(msg.sender) > 0),
"Not authorized"
);
_;
} }
// Events // Events
// External functions // External functions
function setMaxSupply(uint256 newMaxSupply) external onlyOwner { function setMaxSupply(uint256 newMaxSupply) virtual external onlyOwner {
require(newMaxSupply >= totalSupply(), "MAX_SUPPLY_LOWER_THAN_TOTAL_SUPPLY"); require(newMaxSupply >= totalSupply(), "MAX_SUPPLY_LOWER_THAN_TOTAL_SUPPLY");
maxSupply = newMaxSupply; maxSupply = newMaxSupply;
} }
@ -64,15 +83,9 @@ contract CollectibleV1 is
* URI autogenerated based on the base URI passed at construction. * URI autogenerated based on the base URI passed at construction.
* *
*/ */
function mintTo(address[] memory addresses) external onlyOwner { function mintTo(address[] memory addresses) public onlyOwner {
// We cannot just use totalSupply() to create the new tokenId because tokens
// can be burned so we use a separate counter.
require(_tokenIdTracker.current() + addresses.length <= maxSupply, "MAX_SUPPLY_REACHED"); require(_tokenIdTracker.current() + addresses.length <= maxSupply, "MAX_SUPPLY_REACHED");
_mintTo(addresses);
for (uint256 i = 0; i < addresses.length; i++) {
_safeMint(addresses[i], _tokenIdTracker.current(), "");
_tokenIdTracker.increment();
}
} }
// Public functions // Public functions
@ -116,6 +129,15 @@ contract CollectibleV1 is
return baseTokenURI; return baseTokenURI;
} }
function _mintTo(address[] memory addresses) internal {
// We cannot just use totalSupply() to create the new tokenId because tokens
// can be burned so we use a separate counter.
for (uint256 i = 0; i < addresses.length; i++) {
_safeMint(addresses[i], _tokenIdTracker.current(), "");
_tokenIdTracker.increment();
}
}
/** /**
* @notice * @notice
* @dev * @dev

View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: Mozilla Public License 2.0
pragma solidity ^0.8.17;
import "./BaseToken.sol";
contract CollectibleV1 is BaseToken {
constructor(
string memory _name,
string memory _symbol,
uint256 _maxSupply,
bool _remoteBurnable,
bool _transferable,
string memory _baseTokenURI,
address _ownerToken,
address _masterToken
) BaseToken(
_name,
_symbol,
_maxSupply,
_remoteBurnable,
_transferable,
_baseTokenURI,
_ownerToken,
_masterToken) {
}
}

View File

@ -15,12 +15,16 @@ contract CommunityERC20 is
*/ */
uint256 public maxSupply; uint256 public maxSupply;
uint8 private customDecimals;
constructor( constructor(
string memory _name, string memory _name,
string memory _symbol, string memory _symbol,
uint8 _decimals,
uint256 _maxSupply uint256 _maxSupply
) ERC20(_name, _symbol) { ) ERC20(_name, _symbol) {
maxSupply = _maxSupply; maxSupply = _maxSupply;
customDecimals = _decimals;
} }
// Events // Events
@ -48,6 +52,9 @@ contract CommunityERC20 is
} }
// Public functions // Public functions
function decimals() public view virtual override returns (uint8) {
return customDecimals;
}
// Internal functions // Internal functions

View File

@ -0,0 +1,22 @@
// SPDX-License-Identifier: Mozilla Public License 2.0
pragma solidity ^0.8.17;
import "./BaseToken.sol";
contract MasterToken is BaseToken {
constructor(
string memory _name,
string memory _symbol,
string memory _baseTokenURI,
address _ownerToken
) BaseToken(
_name,
_symbol,
type(uint256).max,
true,
false,
_baseTokenURI,
_ownerToken,
address(0x0)) {
}
}

View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: Mozilla Public License 2.0
pragma solidity ^0.8.17;
import "./BaseToken.sol";
import "./MasterToken.sol";
contract OwnerToken is BaseToken {
event MasterTokenCreated(address masterToken);
bytes public signerPublicKey;
constructor(
string memory _name,
string memory _symbol,
string memory _baseTokenURI,
string memory _masterName,
string memory _masterSymbol,
string memory _masterBaseTokenURI,
bytes memory _signerPublicKey
) BaseToken(
_name,
_symbol,
1,
false,
true,
_baseTokenURI,
address(this),
address(this))
{
signerPublicKey = _signerPublicKey;
MasterToken masterToken = new MasterToken(_masterName, _masterSymbol, _masterBaseTokenURI, address(this));
emit MasterTokenCreated(address(masterToken));
address[] memory addresses = new address[](1);
addresses[0] = msg.sender;
_mintTo(addresses);
}
function setMaxSupply(uint256 _newMaxSupply) override external onlyOwner {
revert("max supply locked");
}
function setSignerPublicKey(bytes memory _newSignerPublicKey) external onlyOwner {
signerPublicKey = _newSignerPublicKey;
}
}

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,7 @@ import (
"image/jpeg" "image/jpeg"
"io" "io"
"regexp" "regexp"
"strings"
"github.com/nfnt/resize" "github.com/nfnt/resize"
) )
@ -127,3 +128,7 @@ func GetPayloadFromURI(uri string) ([]byte, error) {
} }
return base64.StdEncoding.DecodeString(res[2]) return base64.StdEncoding.DecodeString(res[2])
} }
func IsPayloadDataURI(uri string) bool {
return strings.HasPrefix(uri, "data:image")
}

View File

@ -4224,7 +4224,8 @@ func (m *Manager) SaveCommunityToken(token *community_token.CommunityToken, crop
return nil, err return nil, err
} }
token.Base64Image = base64img token.Base64Image = base64img
} else { } else if !images.IsPayloadDataURI(token.Base64Image) {
// if image is already base64 do not convert (owner and master tokens have already base64 image)
token.Base64Image = m.ImageToBase64(token.Base64Image) token.Base64Image = m.ImageToBase64(token.Base64Image)
} }
@ -4267,6 +4268,10 @@ func (m *Manager) UpdateCommunityTokenState(chainID int, contractAddress string,
return m.persistence.UpdateCommunityTokenState(chainID, contractAddress, deployState) return m.persistence.UpdateCommunityTokenState(chainID, contractAddress, deployState)
} }
func (m *Manager) UpdateCommunityTokenAddress(chainID int, oldContractAddress string, newContractAddress string) error {
return m.persistence.UpdateCommunityTokenAddress(chainID, oldContractAddress, newContractAddress)
}
func (m *Manager) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply *bigint.BigInt) error { func (m *Manager) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply *bigint.BigInt) error {
return m.persistence.UpdateCommunityTokenSupply(chainID, contractAddress, supply) return m.persistence.UpdateCommunityTokenSupply(chainID, contractAddress, supply)
} }
@ -4486,7 +4491,7 @@ func (m *Manager) HandleCommunityTokensMetadata(communityID string, communityTok
switch tokenMetadata.TokenType { switch tokenMetadata.TokenType {
case protobuf.CommunityTokenType_ERC721: case protobuf.CommunityTokenType_ERC721:
contract, err := m.collectiblesService.API().GetContractInstance(chainID, address) contract, err := m.collectiblesService.API().GetCollectiblesContractInstance(chainID, address)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1179,8 +1179,8 @@ func (p *Persistence) GetCommunityChatIDs(communityID types.HexBytes) ([]string,
func (p *Persistence) GetAllCommunityTokens() ([]*token.CommunityToken, error) { func (p *Persistence) GetAllCommunityTokens() ([]*token.CommunityToken, error) {
rows, err := p.db.Query(`SELECT community_id, address, type, name, symbol, description, supply_str, rows, err := p.db.Query(`SELECT community_id, address, type, name, symbol, description, supply_str,
infinite_supply, transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals infinite_supply, transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals,
FROM community_tokens`) deployer, privileges_level FROM community_tokens`)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1191,7 +1191,8 @@ func (p *Persistence) GetAllCommunityTokens() ([]*token.CommunityToken, error) {
func (p *Persistence) GetCommunityTokens(communityID string) ([]*token.CommunityToken, error) { func (p *Persistence) GetCommunityTokens(communityID string) ([]*token.CommunityToken, error) {
rows, err := p.db.Query(`SELECT community_id, address, type, name, symbol, description, supply_str, rows, err := p.db.Query(`SELECT community_id, address, type, name, symbol, description, supply_str,
infinite_supply, transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals infinite_supply, transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals,
deployer, privileges_level
FROM community_tokens WHERE community_id = ?`, communityID) FROM community_tokens WHERE community_id = ?`, communityID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1204,9 +1205,12 @@ func (p *Persistence) GetCommunityTokens(communityID string) ([]*token.Community
func (p *Persistence) GetCommunityToken(communityID string, chainID int, address string) (*token.CommunityToken, error) { func (p *Persistence) GetCommunityToken(communityID string, chainID int, address string) (*token.CommunityToken, error) {
token := token.CommunityToken{} token := token.CommunityToken{}
var supplyStr string var supplyStr string
err := p.db.QueryRow(`SELECT community_id, address, type, name, symbol, description, supply_str, infinite_supply, transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals FROM community_tokens WHERE community_id = ? AND chain_id = ? AND address = ?`, communityID, chainID, address).Scan(&token.CommunityID, &token.Address, &token.TokenType, &token.Name, err := p.db.QueryRow(`SELECT community_id, address, type, name, symbol, description, supply_str, infinite_supply,
transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals, deployer, privileges_level
FROM community_tokens WHERE community_id = ? AND chain_id = ? AND address = ?`, communityID, chainID, address).Scan(&token.CommunityID, &token.Address, &token.TokenType, &token.Name,
&token.Symbol, &token.Description, &supplyStr, &token.InfiniteSupply, &token.Transferable, &token.Symbol, &token.Description, &supplyStr, &token.InfiniteSupply, &token.Transferable,
&token.RemoteSelfDestruct, &token.ChainID, &token.DeployState, &token.Base64Image, &token.Decimals) &token.RemoteSelfDestruct, &token.ChainID, &token.DeployState, &token.Base64Image, &token.Decimals,
&token.Deployer, &token.PrivilegesLevel)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return nil, nil return nil, nil
} else if err != nil { } else if err != nil {
@ -1229,7 +1233,8 @@ func (p *Persistence) getCommunityTokensInternal(rows *sql.Rows) ([]*token.Commu
var supplyStr string var supplyStr string
err := rows.Scan(&token.CommunityID, &token.Address, &token.TokenType, &token.Name, err := rows.Scan(&token.CommunityID, &token.Address, &token.TokenType, &token.Name,
&token.Symbol, &token.Description, &supplyStr, &token.InfiniteSupply, &token.Transferable, &token.Symbol, &token.Description, &supplyStr, &token.InfiniteSupply, &token.Transferable,
&token.RemoteSelfDestruct, &token.ChainID, &token.DeployState, &token.Base64Image, &token.Decimals) &token.RemoteSelfDestruct, &token.ChainID, &token.DeployState, &token.Base64Image, &token.Decimals,
&token.Deployer, &token.PrivilegesLevel)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1257,10 +1262,10 @@ func (p *Persistence) HasCommunityToken(communityID string, address string, chai
func (p *Persistence) AddCommunityToken(token *token.CommunityToken) error { func (p *Persistence) AddCommunityToken(token *token.CommunityToken) error {
_, err := p.db.Exec(`INSERT INTO community_tokens (community_id, address, type, name, symbol, description, supply_str, _, err := p.db.Exec(`INSERT INTO community_tokens (community_id, address, type, name, symbol, description, supply_str,
infinite_supply, transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals) infinite_supply, transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals, deployer, privileges_level)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, token.CommunityID, token.Address, token.TokenType, token.Name, VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, token.CommunityID, token.Address, token.TokenType, token.Name,
token.Symbol, token.Description, token.Supply.String(), token.InfiniteSupply, token.Transferable, token.RemoteSelfDestruct, token.Symbol, token.Description, token.Supply.String(), token.InfiniteSupply, token.Transferable, token.RemoteSelfDestruct,
token.ChainID, token.DeployState, token.Base64Image, token.Decimals) token.ChainID, token.DeployState, token.Base64Image, token.Decimals, token.Deployer, token.PrivilegesLevel)
return err return err
} }
@ -1269,6 +1274,11 @@ func (p *Persistence) UpdateCommunityTokenState(chainID int, contractAddress str
return err return err
} }
func (p *Persistence) UpdateCommunityTokenAddress(chainID int, oldContractAddress string, newContractAddress string) error {
_, err := p.db.Exec(`UPDATE community_tokens SET address = ? WHERE address = ? AND chain_id = ?`, newContractAddress, oldContractAddress, chainID)
return err
}
func (p *Persistence) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply *bigint.BigInt) error { func (p *Persistence) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply *bigint.BigInt) error {
_, err := p.db.Exec(`UPDATE community_tokens SET supply_str = ? WHERE address = ? AND chain_id = ?`, supply.String(), contractAddress, chainID) _, err := p.db.Exec(`UPDATE community_tokens SET supply_str = ? WHERE address = ? AND chain_id = ?`, supply.String(), contractAddress, chainID)
return err return err

View File

@ -422,6 +422,8 @@ func (s *PersistenceSuite) TestGetCommunityTokens() {
ChainID: 1, ChainID: 1,
DeployState: token.InProgress, DeployState: token.InProgress,
Base64Image: "ABCD", Base64Image: "ABCD",
Deployer: "0xDep1",
PrivilegesLevel: token.OwnerLevel,
} }
tokenERC20 := token.CommunityToken{ tokenERC20 := token.CommunityToken{
@ -439,6 +441,8 @@ func (s *PersistenceSuite) TestGetCommunityTokens() {
DeployState: token.Failed, DeployState: token.Failed,
Base64Image: "QWERTY", Base64Image: "QWERTY",
Decimals: 21, Decimals: 21,
Deployer: "0xDep2",
PrivilegesLevel: token.CommunityLevel,
} }
err = s.db.AddCommunityToken(&tokenERC721) err = s.db.AddCommunityToken(&tokenERC721)
@ -462,6 +466,13 @@ func (s *PersistenceSuite) TestGetCommunityTokens() {
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Len(tokens, 1) s.Require().Len(tokens, 1)
s.Require().Equal(tokenERC20, *tokens[0]) s.Require().Equal(tokenERC20, *tokens[0])
err = s.db.UpdateCommunityTokenAddress(1, "0x123", "0x123-newAddr")
s.Require().NoError(err)
tokens, err = s.db.GetCommunityTokens("123")
s.Require().NoError(err)
s.Require().Len(tokens, 1)
s.Require().Equal("0x123-newAddr", tokens[0].Address)
} }
func (s *PersistenceSuite) TestSaveCheckChannelPermissionResponse() { func (s *PersistenceSuite) TestSaveCheckChannelPermissionResponse() {

View File

@ -13,6 +13,14 @@ const (
Deployed Deployed
) )
type PrivilegesLevel uint8
const (
OwnerLevel PrivilegesLevel = iota
MasterLevel
CommunityLevel
)
type CommunityToken struct { type CommunityToken struct {
TokenType protobuf.CommunityTokenType `json:"tokenType"` TokenType protobuf.CommunityTokenType `json:"tokenType"`
CommunityID string `json:"communityId"` CommunityID string `json:"communityId"`
@ -28,4 +36,6 @@ type CommunityToken struct {
DeployState DeployState `json:"deployState"` DeployState DeployState `json:"deployState"`
Base64Image string `json:"image"` Base64Image string `json:"image"`
Decimals int `json:"decimals"` Decimals int `json:"decimals"`
Deployer string `json:"deployer"`
PrivilegesLevel PrivilegesLevel `json:"privilegesLevel"`
} }

View File

@ -4160,6 +4160,10 @@ func (m *Messenger) UpdateCommunityTokenState(chainID int, contractAddress strin
return m.communitiesManager.UpdateCommunityTokenState(chainID, contractAddress, deployState) return m.communitiesManager.UpdateCommunityTokenState(chainID, contractAddress, deployState)
} }
func (m *Messenger) UpdateCommunityTokenAddress(chainID int, oldContractAddress string, newContractAddress string) error {
return m.communitiesManager.UpdateCommunityTokenAddress(chainID, oldContractAddress, newContractAddress)
}
func (m *Messenger) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply *bigint.BigInt) error { func (m *Messenger) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply *bigint.BigInt) error {
return m.communitiesManager.UpdateCommunityTokenSupply(chainID, contractAddress, supply) return m.communitiesManager.UpdateCommunityTokenSupply(chainID, contractAddress, supply)
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
ALTER TABLE community_tokens ADD COLUMN deployer TEXT NOT NULL DEFAULT "";
ALTER TABLE community_tokens ADD COLUMN privileges_level INT NOT NULL DEFAULT 2;

View File

@ -15,8 +15,10 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/contracts/assets" "github.com/status-im/status-go/contracts/community-tokens/assets"
"github.com/status-im/status-go/contracts/collectibles" "github.com/status-im/status-go/contracts/community-tokens/collectibles"
"github.com/status-im/status-go/contracts/community-tokens/ownertoken"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc"
@ -59,6 +61,8 @@ type DeploymentParameters struct {
Transferable bool `json:"transferable"` Transferable bool `json:"transferable"`
RemoteSelfDestruct bool `json:"remoteSelfDestruct"` RemoteSelfDestruct bool `json:"remoteSelfDestruct"`
TokenURI string `json:"tokenUri"` TokenURI string `json:"tokenUri"`
OwnerTokenAddress string `json:"ownerTokenAddress"`
MasterTokenAddress string `json:"masterTokenAddress"`
} }
func (d *DeploymentParameters) GetSupply() *big.Int { func (d *DeploymentParameters) GetSupply() *big.Int {
@ -115,7 +119,8 @@ func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deployme
address, tx, _, err := collectibles.DeployCollectibles(transactOpts, ethClient, deploymentParameters.Name, address, tx, _, err := collectibles.DeployCollectibles(transactOpts, ethClient, deploymentParameters.Name,
deploymentParameters.Symbol, deploymentParameters.GetSupply(), deploymentParameters.Symbol, deploymentParameters.GetSupply(),
deploymentParameters.RemoteSelfDestruct, deploymentParameters.Transferable, deploymentParameters.RemoteSelfDestruct, deploymentParameters.Transferable,
deploymentParameters.TokenURI) deploymentParameters.TokenURI, common.HexToAddress(deploymentParameters.OwnerTokenAddress),
common.HexToAddress(deploymentParameters.MasterTokenAddress))
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
return DeploymentDetails{}, err return DeploymentDetails{}, err
@ -131,6 +136,77 @@ func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deployme
return DeploymentDetails{address.Hex(), tx.Hash().Hex()}, nil return DeploymentDetails{address.Hex(), tx.Hash().Hex()}, nil
} }
func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) {
err := ownerTokenParameters.Validate(false)
if err != nil {
return DeploymentDetails{}, err
}
err = masterTokenParameters.Validate(false)
if err != nil {
return DeploymentDetails{}, err
}
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.accountsManager, api.config.KeyStoreDir, txArgs.From, password))
ethClient, err := api.RPCClient.EthClient(chainID)
if err != nil {
log.Error(err.Error())
return DeploymentDetails{}, err
}
signerPubKey := []byte{}
address, tx, _, err := ownertoken.DeployOwnerToken(transactOpts, ethClient, ownerTokenParameters.Name,
ownerTokenParameters.Symbol, ownerTokenParameters.TokenURI,
masterTokenParameters.Name, masterTokenParameters.Symbol,
masterTokenParameters.TokenURI, signerPubKey)
if err != nil {
log.Error(err.Error())
return DeploymentDetails{}, err
}
go api.rpcFiltersSrvc.TriggerTransactionSentToUpstreamEvent(&rpcfilters.PendingTxInfo{
Hash: tx.Hash(),
Type: string(transactions.DeployOwnerToken),
From: common.Address(txArgs.From),
ChainID: chainID,
})
return DeploymentDetails{address.Hex(), tx.Hash().Hex()}, nil
}
func (api *API) GetMasterTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) {
ethClient, err := api.RPCClient.EthClient(chainID)
if err != nil {
return "", err
}
receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash))
if err != nil {
return "", err
}
logMasterTokenCreatedSig := []byte("MasterTokenCreated(address)")
logMasterTokenCreatedSigHash := crypto.Keccak256Hash(logMasterTokenCreatedSig)
for _, vLog := range receipt.Logs {
if vLog.Topics[0].Hex() == logMasterTokenCreatedSigHash.Hex() {
ownerTokenABI, err := abi.JSON(strings.NewReader(ownertoken.OwnerTokenABI))
if err != nil {
return "", err
}
event := new(ownertoken.OwnerTokenMasterTokenCreated)
err = ownerTokenABI.UnpackIntoInterface(event, "MasterTokenCreated", vLog.Data)
if err != nil {
return "", err
}
return event.MasterToken.Hex(), nil
}
}
return "", fmt.Errorf("can't find master token address in transaction: %v", txHash)
}
func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentParameters DeploymentParameters, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) { func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentParameters DeploymentParameters, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) {
err := deploymentParameters.Validate(true) err := deploymentParameters.Validate(true)
@ -165,7 +241,7 @@ func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentPara
// Returns gas units + 10% // Returns gas units + 10%
func (api *API) DeployCollectiblesEstimate(ctx context.Context) (uint64, error) { func (api *API) DeployCollectiblesEstimate(ctx context.Context) (uint64, error) {
gasAmount := uint64(1960645) gasAmount := uint64(2091605)
return gasAmount + uint64(float32(gasAmount)*0.1), nil return gasAmount + uint64(float32(gasAmount)*0.1), nil
} }
@ -175,6 +251,12 @@ func (api *API) DeployAssetsEstimate(ctx context.Context) (uint64, error) {
return gasAmount + uint64(float32(gasAmount)*0.1), nil return gasAmount + uint64(float32(gasAmount)*0.1), nil
} }
// Returns gas units + 10%
func (api *API) DeployOwnerTokenEstimate(ctx context.Context) (uint64, error) {
ownerGasAmount := uint64(4389457)
return ownerGasAmount + uint64(float32(ownerGasAmount)*0.1), nil
}
func (api *API) newCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { func (api *API) newCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
backend, err := api.RPCClient.EthClient(chainID) backend, err := api.RPCClient.EthClient(chainID)
if err != nil { if err != nil {
@ -228,16 +310,16 @@ func (api *API) MintTokens(ctx context.Context, chainID uint64, contractAddress
} }
} }
func (api *API) EstimateMintTokens(ctx context.Context, chainID uint64, contractAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) { func (api *API) EstimateMintTokens(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
tokenType, err := api.db.GetTokenType(chainID, contractAddress) tokenType, err := api.db.GetTokenType(chainID, contractAddress)
if err != nil { if err != nil {
return 0, err return 0, err
} }
switch tokenType { switch tokenType {
case protobuf.CommunityTokenType_ERC721: case protobuf.CommunityTokenType_ERC721:
return api.EstimateMintCollectibles(ctx, chainID, contractAddress, walletAddresses, amount) return api.EstimateMintCollectibles(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount)
case protobuf.CommunityTokenType_ERC20: case protobuf.CommunityTokenType_ERC20:
return api.EstimateMintAssets(ctx, chainID, contractAddress, walletAddresses, amount) return api.EstimateMintAssets(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount)
default: default:
return 0, fmt.Errorf("unknown token type: %v", tokenType) return 0, fmt.Errorf("unknown token type: %v", tokenType)
} }
@ -274,13 +356,13 @@ func (api *API) MintCollectibles(ctx context.Context, chainID uint64, contractAd
return tx.Hash().Hex(), nil return tx.Hash().Hex(), nil
} }
func (api *API) EstimateMintCollectibles(ctx context.Context, chainID uint64, contractAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) { func (api *API) EstimateMintCollectibles(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
err := api.validateWalletsAndAmounts(walletAddresses, amount) err := api.validateWalletsAndAmounts(walletAddresses, amount)
if err != nil { if err != nil {
return 0, err return 0, err
} }
usersAddresses := api.prepareMintCollectiblesData(walletAddresses, amount) usersAddresses := api.prepareMintCollectiblesData(walletAddresses, amount)
return api.estimateMethod(ctx, chainID, contractAddress, "mintTo", usersAddresses) return api.estimateMethod(ctx, chainID, contractAddress, fromAddress, "mintTo", usersAddresses)
} }
func (api *API) prepareMintAssetsData(walletAddresses []string, amount *bigint.BigInt) ([]common.Address, []*big.Int) { func (api *API) prepareMintAssetsData(walletAddresses []string, amount *bigint.BigInt) ([]common.Address, []*big.Int) {
@ -327,13 +409,13 @@ func (api *API) MintAssets(ctx context.Context, chainID uint64, contractAddress
} }
// Estimate MintAssets cost. // Estimate MintAssets cost.
func (api *API) EstimateMintAssets(ctx context.Context, chainID uint64, contractAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) { func (api *API) EstimateMintAssets(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
err := api.validateWalletsAndAmounts(walletAddresses, amount) err := api.validateWalletsAndAmounts(walletAddresses, amount)
if err != nil { if err != nil {
return 0, err return 0, err
} }
usersAddresses, amountsList := api.prepareMintAssetsData(walletAddresses, amount) usersAddresses, amountsList := api.prepareMintAssetsData(walletAddresses, amount)
return api.estimateMethod(ctx, chainID, contractAddress, "mintTo", usersAddresses, amountsList) return api.estimateMethod(ctx, chainID, contractAddress, fromAddress, "mintTo", usersAddresses, amountsList)
} }
// This is only ERC721 function // This is only ERC721 function
@ -397,7 +479,7 @@ func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress
} }
// This is only ERC721 function // This is only ERC721 function
func (api *API) EstimateRemoteBurn(ctx context.Context, chainID uint64, contractAddress string, tokenIds []*bigint.BigInt) (uint64, error) { func (api *API) EstimateRemoteBurn(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, tokenIds []*bigint.BigInt) (uint64, error) {
err := api.validateTokens(tokenIds) err := api.validateTokens(tokenIds)
if err != nil { if err != nil {
return 0, err return 0, err
@ -408,10 +490,10 @@ func (api *API) EstimateRemoteBurn(ctx context.Context, chainID uint64, contract
tempTokenIds = append(tempTokenIds, v.Int) tempTokenIds = append(tempTokenIds, v.Int)
} }
return api.estimateMethod(ctx, chainID, contractAddress, "remoteBurn", tempTokenIds) return api.estimateMethod(ctx, chainID, contractAddress, fromAddress, "remoteBurn", tempTokenIds)
} }
func (api *API) GetContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { func (api *API) GetCollectiblesContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
contractInst, err := api.newCollectiblesInstance(chainID, contractAddress) contractInst, err := api.newCollectiblesInstance(chainID, contractAddress)
if err != nil { if err != nil {
return nil, err return nil, err
@ -427,36 +509,6 @@ func (api *API) GetAssetContractInstance(chainID uint64, contractAddress string)
return contractInst, nil return contractInst, nil
} }
func (api *API) ContractOwner(ctx context.Context, chainID uint64, contractAddress string) (string, error) {
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
tokenType, err := api.db.GetTokenType(chainID, contractAddress)
if err != nil {
return "", err
}
if tokenType == protobuf.CommunityTokenType_ERC721 {
contractInst, err := api.newCollectiblesInstance(chainID, contractAddress)
if err != nil {
return "", err
}
owner, err := contractInst.Owner(callOpts)
if err != nil {
return "", err
}
return owner.String(), nil
} else if tokenType == protobuf.CommunityTokenType_ERC20 {
contractInst, err := api.newAssetsInstance(chainID, contractAddress)
if err != nil {
return "", err
}
owner, err := contractInst.Owner(callOpts)
if err != nil {
return "", err
}
return owner.String(), nil
}
return "", fmt.Errorf("unknown token type: %v", tokenType)
}
func (api *API) RemainingSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { func (api *API) RemainingSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) {
tokenType, err := api.db.GetTokenType(chainID, contractAddress) tokenType, err := api.db.GetTokenType(chainID, contractAddress)
if err != nil { if err != nil {
@ -616,7 +668,7 @@ func (api *API) Burn(ctx context.Context, chainID uint64, contractAddress string
return tx.Hash().Hex(), nil return tx.Hash().Hex(), nil
} }
func (api *API) EstimateBurn(ctx context.Context, chainID uint64, contractAddress string, burnAmount *bigint.BigInt) (uint64, error) { func (api *API) EstimateBurn(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, burnAmount *bigint.BigInt) (uint64, error) {
err := api.validateBurnAmount(ctx, burnAmount, chainID, contractAddress) err := api.validateBurnAmount(ctx, burnAmount, chainID, contractAddress)
if err != nil { if err != nil {
return 0, err return 0, err
@ -627,7 +679,7 @@ func (api *API) EstimateBurn(ctx context.Context, chainID uint64, contractAddres
return 0, err return 0, err
} }
return api.estimateMethod(ctx, chainID, contractAddress, "setMaxSupply", newMaxSupply) return api.estimateMethod(ctx, chainID, contractAddress, fromAddress, "setMaxSupply", newMaxSupply)
} }
func (api *API) validateWalletsAndAmounts(walletAddresses []string, amount *bigint.BigInt) error { func (api *API) validateWalletsAndAmounts(walletAddresses []string, amount *bigint.BigInt) error {
@ -677,7 +729,7 @@ func (api *API) packAssetsMethod(ctx context.Context, methodName string, args ..
return assetsABI.Pack(methodName, args...) return assetsABI.Pack(methodName, args...)
} }
func (api *API) estimateMethod(ctx context.Context, chainID uint64, contractAddress string, methodName string, args ...interface{}) (uint64, error) { func (api *API) estimateMethod(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, methodName string, args ...interface{}) (uint64, error) {
ethClient, err := api.RPCClient.EthClient(chainID) ethClient, err := api.RPCClient.EthClient(chainID)
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
@ -702,13 +754,8 @@ func (api *API) estimateMethod(ctx context.Context, chainID uint64, contractAddr
return 0, err return 0, err
} }
ownerAddr, err := api.ContractOwner(ctx, chainID, contractAddress)
if err != nil {
return 0, err
}
toAddr := common.HexToAddress(contractAddress) toAddr := common.HexToAddress(contractAddress)
fromAddr := common.HexToAddress(ownerAddr) fromAddr := common.HexToAddress(fromAddress)
callMsg := ethereum.CallMsg{ callMsg := ethereum.CallMsg{
From: fromAddr, From: fromAddr,

View File

@ -17,37 +17,37 @@ func TestDeploymentParameters(t *testing.T) {
}{ }{
{ {
name: "emptyName", name: "emptyName",
parameters: DeploymentParameters{"", "SYMBOL", &bigint.BigInt{Int: big.NewInt(int64(123))}, false, false, false, ""}, parameters: DeploymentParameters{"", "SYMBOL", &bigint.BigInt{Int: big.NewInt(int64(123))}, false, false, false, "", "", ""},
isError: true, isError: true,
}, },
{ {
name: "emptySymbol", name: "emptySymbol",
parameters: DeploymentParameters{"NAME", "", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, ""}, parameters: DeploymentParameters{"NAME", "", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, "", "", ""},
isError: true, isError: true,
}, },
{ {
name: "negativeSupply", name: "negativeSupply",
parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, false, false, false, ""}, parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, false, false, false, "", "", ""},
isError: true, isError: true,
}, },
{ {
name: "zeroSupply", name: "zeroSupply",
parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(0)}, false, false, false, ""}, parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(0)}, false, false, false, "", "", ""},
isError: false, isError: false,
}, },
{ {
name: "negativeSupplyAndInfinite", name: "negativeSupplyAndInfinite",
parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, true, false, false, ""}, parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, true, false, false, "", "", ""},
isError: false, isError: false,
}, },
{ {
name: "supplyGreaterThanMax", name: "supplyGreaterThanMax",
parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply + 1)}, false, false, false, ""}, parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply + 1)}, false, false, false, "", "", ""},
isError: true, isError: true,
}, },
{ {
name: "supplyIsMax", name: "supplyIsMax",
parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply)}, false, false, false, ""}, parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply)}, false, false, false, "", "", ""},
isError: false, isError: false,
}, },
} }
@ -63,10 +63,10 @@ func TestDeploymentParameters(t *testing.T) {
}) })
} }
notInfiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, ""} notInfiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, "", "", ""}
requiredSupply := big.NewInt(123) requiredSupply := big.NewInt(123)
require.Equal(t, notInfiniteSupplyParams.GetSupply(), requiredSupply) require.Equal(t, notInfiniteSupplyParams.GetSupply(), requiredSupply)
infiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, true, false, false, ""} infiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, true, false, false, "", "", ""}
requiredSupply = infiniteSupplyParams.GetInfiniteSupply() requiredSupply = infiniteSupplyParams.GetInfiniteSupply()
require.Equal(t, infiniteSupplyParams.GetSupply(), requiredSupply) require.Equal(t, infiniteSupplyParams.GetSupply(), requiredSupply)
} }

View File

@ -27,10 +27,10 @@ type DatabaseSuite struct {
func (s *DatabaseSuite) addCommunityToken(db *sql.DB, token *token.CommunityToken) error { func (s *DatabaseSuite) addCommunityToken(db *sql.DB, token *token.CommunityToken) error {
_, err := db.Exec(`INSERT INTO community_tokens (community_id, address, type, name, symbol, description, supply_str, _, err := db.Exec(`INSERT INTO community_tokens (community_id, address, type, name, symbol, description, supply_str,
infinite_supply, transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals) infinite_supply, transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals, deployer, privileges_level)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, token.CommunityID, token.Address, token.TokenType, token.Name, VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, token.CommunityID, token.Address, token.TokenType, token.Name,
token.Symbol, token.Description, token.Supply.String(), token.InfiniteSupply, token.Transferable, token.RemoteSelfDestruct, token.Symbol, token.Description, token.Supply.String(), token.InfiniteSupply, token.Transferable, token.RemoteSelfDestruct,
token.ChainID, token.DeployState, token.Base64Image, token.Decimals) token.ChainID, token.DeployState, token.Base64Image, token.Decimals, token.Deployer, token.PrivilegesLevel)
return err return err
} }
@ -49,6 +49,8 @@ func (s *DatabaseSuite) setupDatabase(db *sql.DB) error {
ChainID: 1, ChainID: 1,
DeployState: token.InProgress, DeployState: token.InProgress,
Base64Image: "ABCD", Base64Image: "ABCD",
Deployer: "0xDEP1",
PrivilegesLevel: token.OwnerLevel,
} }
token20 := &token.CommunityToken{ token20 := &token.CommunityToken{
@ -66,6 +68,8 @@ func (s *DatabaseSuite) setupDatabase(db *sql.DB) error {
DeployState: token.Failed, DeployState: token.Failed,
Base64Image: "QWERTY", Base64Image: "QWERTY",
Decimals: 21, Decimals: 21,
Deployer: "0xDEP2",
PrivilegesLevel: token.CommunityLevel,
} }
err := s.addCommunityToken(db, token721) err := s.addCommunityToken(db, token721)

View File

@ -1353,6 +1353,10 @@ func (api *PublicAPI) UpdateCommunityTokenState(chainID int, contractAddress str
return api.service.messenger.UpdateCommunityTokenState(chainID, contractAddress, deployState) return api.service.messenger.UpdateCommunityTokenState(chainID, contractAddress, deployState)
} }
func (api *PublicAPI) UpdateCommunityTokenAddress(chainID int, oldContractAddress string, newContractAddress string) error {
return api.service.messenger.UpdateCommunityTokenAddress(chainID, oldContractAddress, newContractAddress)
}
func (api *PublicAPI) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply *bigint.BigInt) error { func (api *PublicAPI) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply *bigint.BigInt) error {
return api.service.messenger.UpdateCommunityTokenSupply(chainID, contractAddress, supply) return api.service.messenger.UpdateCommunityTokenSupply(chainID, contractAddress, supply)
} }

View File

@ -15,7 +15,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/contracts/collectibles" "github.com/status-im/status-go/contracts/community-tokens/collectibles"
"github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/bigint"
walletCommon "github.com/status-im/status-go/services/wallet/common" walletCommon "github.com/status-im/status-go/services/wallet/common"

View File

@ -103,6 +103,7 @@ const (
AirdropCommunityToken PendingTrxType = "AirdropCommunityToken" AirdropCommunityToken PendingTrxType = "AirdropCommunityToken"
RemoteDestructCollectible PendingTrxType = "RemoteDestructCollectible" RemoteDestructCollectible PendingTrxType = "RemoteDestructCollectible"
BurnCommunityToken PendingTrxType = "BurnCommunityToken" BurnCommunityToken PendingTrxType = "BurnCommunityToken"
DeployOwnerToken PendingTrxType = "DeployOwnerToken"
) )
type PendingTransaction struct { type PendingTransaction struct {