feat(BurnCollectibles): Add burn collectibles functionality

Change smart contract with new API.
Update gas amount for deployment.
Add Burn() and EstimateBurn() functions.
Add RemainingSupply() functions.

Issue #10816
This commit is contained in:
Michal Iskierko 2023-06-02 10:07:00 +02:00 committed by Michał Iskierko
parent 8015cc3e3b
commit ccde92377d
10 changed files with 217 additions and 65 deletions

View File

@ -1 +1 @@
0.157.4 0.158.0

View File

@ -53,6 +53,11 @@ contract CollectibleV1 is
// External functions // External functions
function setMaxSupply(uint256 newMaxSupply) external onlyOwner {
require(newMaxSupply >= totalSupply(), "MAX_SUPPLY_LOWER_THAN_TOTAL_SUPPLY");
maxSupply = newMaxSupply;
}
/** /**
* @dev Creates a new token for each address in `addresses`. Its token ID will be automatically * @dev Creates a new token for each address in `addresses`. Its token ID will be automatically
* assigned (and available on the emitted {IERC721-Transfer} event), and the token * assigned (and available on the emitted {IERC721-Transfer} event), and the token
@ -62,7 +67,7 @@ contract CollectibleV1 is
function mintTo(address[] memory addresses) external onlyOwner { function mintTo(address[] memory addresses) external onlyOwner {
// We cannot just use totalSupply() to create the new tokenId because tokens // We cannot just use totalSupply() to create the new tokenId because tokens
// can be burned so we use a separate counter. // 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");
for (uint256 i = 0; i < addresses.length; i++) { for (uint256 i = 0; i < addresses.length; i++) {
_safeMint(addresses[i], _tokenIdTracker.current(), ""); _safeMint(addresses[i], _tokenIdTracker.current(), "");
@ -72,6 +77,10 @@ contract CollectibleV1 is
// Public functions // Public functions
function mintedCount() public view returns (uint256) {
return _tokenIdTracker.current();
}
/** /**
* @notice remoteBurn allows the owner to burn a token * @notice remoteBurn allows the owner to burn a token
* @param tokenIds The list of token IDs to be burned * @param tokenIds The list of token IDs to be burned
@ -117,6 +126,9 @@ contract CollectibleV1 is
uint256 firstTokenId, uint256 firstTokenId,
uint256 batchSize uint256 batchSize
) internal virtual override(ERC721Enumerable) { ) internal virtual override(ERC721Enumerable) {
if (from != address(0) && to != address(0) && !transferable) {
revert("not transferable");
}
super._beforeTokenTransfer(from, to, firstTokenId, batchSize); super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
} }

File diff suppressed because one or more lines are too long

View File

@ -3845,8 +3845,12 @@ func (m *Manager) AddCommunityToken(token *CommunityToken) (*CommunityToken, err
return token, m.persistence.AddCommunityToken(token) return token, m.persistence.AddCommunityToken(token)
} }
func (m *Manager) UpdateCommunityTokenState(contractAddress string, deployState DeployState) error { func (m *Manager) UpdateCommunityTokenState(chainID int, contractAddress string, deployState DeployState) error {
return m.persistence.UpdateCommunityTokenState(contractAddress, deployState) return m.persistence.UpdateCommunityTokenState(chainID, contractAddress, deployState)
}
func (m *Manager) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply int) error {
return m.persistence.UpdateCommunityTokenSupply(chainID, contractAddress, supply)
} }
func (m *Manager) SetCommunityActiveMembersCount(communityID string, activeMembersCount uint64) error { func (m *Manager) SetCommunityActiveMembersCount(communityID string, activeMembersCount uint64) error {

View File

@ -988,7 +988,12 @@ func (p *Persistence) AddCommunityToken(token *CommunityToken) error {
return err return err
} }
func (p *Persistence) UpdateCommunityTokenState(contractAddress string, deployState DeployState) error { func (p *Persistence) UpdateCommunityTokenState(chainID int, contractAddress string, deployState DeployState) error {
_, err := p.db.Exec(`UPDATE community_tokens SET deploy_state = ? WHERE address = ?`, deployState, contractAddress) _, err := p.db.Exec(`UPDATE community_tokens SET deploy_state = ? WHERE address = ? AND chain_id = ?`, deployState, contractAddress, chainID)
return err
}
func (p *Persistence) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply int) error {
_, err := p.db.Exec(`UPDATE community_tokens SET supply = ? WHERE address = ? AND chain_id = ?`, supply, contractAddress, chainID)
return err return err
} }

View File

@ -417,7 +417,7 @@ func (s *PersistenceSuite) TestGetCommunityTokens() {
s.Require().Len(tokens, 1) s.Require().Len(tokens, 1)
s.Require().Equal(token, *tokens[0]) s.Require().Equal(token, *tokens[0])
err = s.db.UpdateCommunityTokenState("0x123", Deployed) err = s.db.UpdateCommunityTokenState(1, "0x123", Deployed)
s.Require().NoError(err) s.Require().NoError(err)
tokens, err = s.db.GetCommunityTokens("123") tokens, err = s.db.GetCommunityTokens("123")
s.Require().NoError(err) s.Require().NoError(err)

View File

@ -3731,8 +3731,12 @@ func (m *Messenger) AddCommunityToken(token *communities.CommunityToken) (*commu
return m.communitiesManager.AddCommunityToken(token) return m.communitiesManager.AddCommunityToken(token)
} }
func (m *Messenger) UpdateCommunityTokenState(contractAddress string, deployState communities.DeployState) error { func (m *Messenger) UpdateCommunityTokenState(chainID int, contractAddress string, deployState communities.DeployState) error {
return m.communitiesManager.UpdateCommunityTokenState(contractAddress, deployState) return m.communitiesManager.UpdateCommunityTokenState(chainID, contractAddress, deployState)
}
func (m *Messenger) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply int) error {
return m.communitiesManager.UpdateCommunityTokenSupply(chainID, contractAddress, supply)
} }
// UpdateCommunityEncryption takes a community and encrypts / decrypts the community // UpdateCommunityEncryption takes a community and encrypts / decrypts the community

View File

@ -111,7 +111,8 @@ func (api *API) Deploy(ctx context.Context, chainID uint64, deploymentParameters
// 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) {
return 3702411, nil gasAmount := uint64(1960645)
return gasAmount + uint64(float32(gasAmount)*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) {
@ -166,17 +167,6 @@ func (api *API) EstimateMintTo(ctx context.Context, chainID uint64, contractAddr
return 0, err return 0, err
} }
ethClient, err := api.RPCClient.EthClient(chainID)
if err != nil {
log.Error(err.Error())
return 0, err
}
collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI))
if err != nil {
return 0, err
}
totalAddresses := api.multiplyWalletAddresses(amount, walletAddresses) totalAddresses := api.multiplyWalletAddresses(amount, walletAddresses)
var usersAddresses = []common.Address{} var usersAddresses = []common.Address{}
@ -184,35 +174,13 @@ func (api *API) EstimateMintTo(ctx context.Context, chainID uint64, contractAddr
usersAddresses = append(usersAddresses, common.HexToAddress(k)) usersAddresses = append(usersAddresses, common.HexToAddress(k))
} }
data, err := collectiblesABI.Pack("mintTo", usersAddresses) return api.estimateMethod(ctx, chainID, contractAddress, "mintTo", usersAddresses)
if err != nil {
return 0, err
}
ownerAddr, err := api.ContractOwner(ctx, chainID, contractAddress)
if err != nil {
return 0, err
}
toAddr := common.HexToAddress(contractAddress)
fromAddr := common.HexToAddress(ownerAddr)
callMsg := ethereum.CallMsg{
From: fromAddr,
To: &toAddr,
Value: big.NewInt(0),
Data: data,
}
estimate, err := ethClient.EstimateGas(ctx, callMsg)
if err != nil {
return 0, err
}
return estimate + uint64(float32(estimate)*0.1), nil
} }
func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, tokenIds []*bigint.BigInt) (string, error) { func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, tokenIds []*bigint.BigInt) (string, error) {
if len(tokenIds) == 0 { err := api.validateTokens(tokenIds)
return "", errors.New("tokenIds list is empty") if err != nil {
return "", err
} }
contractInst, err := api.newCollectiblesInstance(chainID, contractAddress) contractInst, err := api.newCollectiblesInstance(chainID, contractAddress)
@ -235,6 +203,20 @@ func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress
return tx.Hash().Hex(), nil return tx.Hash().Hex(), nil
} }
func (api *API) EstimateRemoteBurn(ctx context.Context, chainID uint64, contractAddress string, tokenIds []*bigint.BigInt) (uint64, error) {
err := api.validateTokens(tokenIds)
if err != nil {
return 0, err
}
var tempTokenIds []*big.Int
for _, v := range tokenIds {
tempTokenIds = append(tempTokenIds, v.Int)
}
return api.estimateMethod(ctx, chainID, contractAddress, "remoteBurn", tempTokenIds)
}
func (api *API) ContractOwner(ctx context.Context, chainID uint64, contractAddress string) (string, error) { func (api *API) ContractOwner(ctx context.Context, chainID uint64, contractAddress string) (string, error) {
callOpts := &bind.CallOpts{Context: ctx, Pending: false} callOpts := &bind.CallOpts{Context: ctx, Pending: false}
contractInst, err := api.newCollectiblesInstance(chainID, contractAddress) contractInst, err := api.newCollectiblesInstance(chainID, contractAddress)
@ -248,14 +230,90 @@ func (api *API) ContractOwner(ctx context.Context, chainID uint64, contractAddre
return owner.String(), nil return owner.String(), nil
} }
func (api *API) AddTokenOwners(ctx context.Context, chainID uint64, contractAddress string, walletAddresses []string, amount int) error { func (api *API) MintedCount(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) {
err := api.validateWalletsAndAmounts(walletAddresses, amount) callOpts := &bind.CallOpts{Context: ctx, Pending: false}
contractInst, err := api.newCollectiblesInstance(chainID, contractAddress)
if err != nil { if err != nil {
return err return nil, err
}
mintedCount, err := contractInst.MintedCount(callOpts)
if err != nil {
return nil, err
}
return mintedCount, nil
}
// RemainingSupply = MaxSupply - MintedCount
func (api *API) RemainingSupply(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) {
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
contractInst, err := api.newCollectiblesInstance(chainID, contractAddress)
if err != nil {
return nil, err
}
maxSupply, err := contractInst.MaxSupply(callOpts)
if err != nil {
return nil, err
}
mintedCount, err := contractInst.MintedCount(callOpts)
if err != nil {
return nil, err
}
var res = new(big.Int)
res.Sub(maxSupply, mintedCount)
return res, nil
}
func (api *API) maxSupply(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) {
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
contractInst, err := api.newCollectiblesInstance(chainID, contractAddress)
if err != nil {
return nil, err
}
return contractInst.MaxSupply(callOpts)
}
func (api *API) Burn(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, burnAmount *bigint.BigInt) (string, error) {
err := api.validateBurnAmount(ctx, burnAmount, chainID, contractAddress)
if err != nil {
return "", err
} }
totalAddresses := api.multiplyWalletAddresses(amount, walletAddresses) contractInst, err := api.newCollectiblesInstance(chainID, contractAddress)
return api.db.AddTokenOwners(chainID, contractAddress, totalAddresses) if err != nil {
return "", err
}
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.accountsManager, api.config.KeyStoreDir, txArgs.From, password))
maxSupply, err := api.maxSupply(ctx, chainID, contractAddress)
if err != nil {
return "", err
}
var newMaxSupply = new(big.Int)
newMaxSupply.Sub(maxSupply, burnAmount.Int)
tx, err := contractInst.SetMaxSupply(transactOpts, newMaxSupply)
if err != nil {
return "", err
}
return tx.Hash().Hex(), nil
}
func (api *API) EstimateBurn(ctx context.Context, chainID uint64, contractAddress string, burnAmount *bigint.BigInt) (uint64, error) {
err := api.validateBurnAmount(ctx, burnAmount, chainID, contractAddress)
if err != nil {
return 0, err
}
maxSupply, err := api.maxSupply(ctx, chainID, contractAddress)
if err != nil {
return 0, err
}
var newMaxSupply = new(big.Int)
newMaxSupply.Sub(maxSupply, burnAmount.Int)
return api.estimateMethod(ctx, chainID, contractAddress, "setMaxSupply", newMaxSupply)
} }
func (api *API) validateWalletsAndAmounts(walletAddresses []string, amount int) error { func (api *API) validateWalletsAndAmounts(walletAddresses []string, amount int) error {
@ -268,11 +326,28 @@ func (api *API) validateWalletsAndAmounts(walletAddresses []string, amount int)
return nil return nil
} }
func (api *API) EstimateRemoteBurn(ctx context.Context, chainID uint64, contractAddress string, tokenIds []*bigint.BigInt) (uint64, error) { func (api *API) validateTokens(tokenIds []*bigint.BigInt) error {
if len(tokenIds) == 0 { if len(tokenIds) == 0 {
return 0, errors.New("token list is empty") return errors.New("token list is empty")
} }
return nil
}
func (api *API) validateBurnAmount(ctx context.Context, burnAmount *bigint.BigInt, chainID uint64, contractAddress string) error {
if burnAmount.Cmp(big.NewInt(0)) <= 0 {
return errors.New("burnAmount is less than 0")
}
remainingSupply, err := api.RemainingSupply(ctx, chainID, contractAddress)
if err != nil {
return err
}
if burnAmount.Cmp(remainingSupply) > 1 {
return errors.New("burnAmount is bigger than remaining amount")
}
return nil
}
func (api *API) estimateMethod(ctx context.Context, chainID uint64, contractAddress 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())
@ -284,12 +359,7 @@ func (api *API) EstimateRemoteBurn(ctx context.Context, chainID uint64, contract
return 0, err return 0, err
} }
var tempTokenIds []*big.Int data, err := collectiblesABI.Pack(methodName, args...)
for _, v := range tokenIds {
tempTokenIds = append(tempTokenIds, v.Int)
}
data, err := collectiblesABI.Pack("remoteBurn", tempTokenIds)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -298,6 +368,7 @@ func (api *API) EstimateRemoteBurn(ctx context.Context, chainID uint64, contract
if err != nil { if err != nil {
return 0, err return 0, err
} }
toAddr := common.HexToAddress(contractAddress) toAddr := common.HexToAddress(contractAddress)
fromAddr := common.HexToAddress(ownerAddr) fromAddr := common.HexToAddress(ownerAddr)
@ -307,7 +378,6 @@ func (api *API) EstimateRemoteBurn(ctx context.Context, chainID uint64, contract
Value: big.NewInt(0), Value: big.NewInt(0),
Data: data, Data: data,
} }
estimate, err := ethClient.EstimateGas(ctx, callMsg) estimate, err := ethClient.EstimateGas(ctx, callMsg)
if err != nil { if err != nil {
return 0, err return 0, err

View File

@ -1309,8 +1309,12 @@ func (api *PublicAPI) AddCommunityToken(token *communities.CommunityToken) (*com
return api.service.messenger.AddCommunityToken(token) return api.service.messenger.AddCommunityToken(token)
} }
func (api *PublicAPI) UpdateCommunityTokenState(contractAddress string, deployState communities.DeployState) error { func (api *PublicAPI) UpdateCommunityTokenState(chainID int, contractAddress string, deployState communities.DeployState) error {
return api.service.messenger.UpdateCommunityTokenState(contractAddress, deployState) return api.service.messenger.UpdateCommunityTokenState(chainID, contractAddress, deployState)
}
func (api *PublicAPI) UpdateCommunityTokenSupply(chainID int, contractAddress string, supply int) error {
return api.service.messenger.UpdateCommunityTokenSupply(chainID, contractAddress, supply)
} }
func (api *PublicAPI) ToggleCollapsedCommunityCategory(request *requests.ToggleCollapsedCommunityCategory) error { func (api *PublicAPI) ToggleCollapsedCommunityCategory(request *requests.ToggleCollapsedCommunityCategory) error {

View File

@ -89,6 +89,7 @@ const (
CollectibleDeployment PendingTrxType = "CollectibleDeployment" CollectibleDeployment PendingTrxType = "CollectibleDeployment"
CollectibleAirdrop PendingTrxType = "CollectibleAirdrop" CollectibleAirdrop PendingTrxType = "CollectibleAirdrop"
CollectibleRemoteSelfDestruct PendingTrxType = "CollectibleRemoteSelfDestruct" CollectibleRemoteSelfDestruct PendingTrxType = "CollectibleRemoteSelfDestruct"
CollectibleBurn PendingTrxType = "CollectibleBurn"
) )
type PendingTransaction struct { type PendingTransaction struct {