From a49432ce50dcfa41bbf1d120c49ba595efd3c103 Mon Sep 17 00:00:00 2001 From: marc0olo Date: Wed, 5 Jul 2023 10:58:12 +0200 Subject: [PATCH] burn & burn_multiple_nfts logic + test --- .../contracts/AENSWrapping.aes | 30 ++++++----- .../smart-contracts/test/aensWrappingTest.js | 51 +++++++++++++++++++ 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/development/smart-contracts/contracts/AENSWrapping.aes b/development/smart-contracts/contracts/AENSWrapping.aes index 14e5487..68da9f0 100644 --- a/development/smart-contracts/contracts/AENSWrapping.aes +++ b/development/smart-contracts/contracts/AENSWrapping.aes @@ -177,13 +177,7 @@ main contract AENSWrapping : IAEX141, IAENSWrapping = // burnable extension stateful entrypoint burn(token_id: int) = - let owner = require_authorized(token_id) - __remove_approval(token_id) - put(state{ balances[owner] @balance = balance - 1 - , total_supply = state.total_supply - 1 - , token_to_owner = Map.delete(token_id, state.token_to_owner) - , owner_to_tokens[owner] @owners_tokens = Set.delete(token_id, owners_tokens) }) - Chain.event(Burn(owner, token_id)) + __burn_single(token_id) // AENSWrapping @@ -513,8 +507,7 @@ main contract AENSWrapping : IAEX141, IAENSWrapping = /// @notice burns a set of NFTs (only possible if AENS names are expired) /// @param nft_ids the ids of the NFTs to burn stateful entrypoint burn_multiple_nfts(nft_ids: Set.set(int)) = - // TODO - () + List.foreach(Set.to_list(nft_ids), (id) => __burn_single(id)) // external helpers for AEX-141 @@ -523,6 +516,12 @@ main contract AENSWrapping : IAEX141, IAENSWrapping = // internal helper functions + function require_expired_if_wrapped(nft_id: int) = + let Some(MetadataMap(nft_metadata_map)) = Map.lookup(nft_id, state.metadata) + if(Map.size(nft_metadata_map) > 0) + let Some(FixedTTL(expiration_height)) = Map.lookup(nft_id, state.token_to_name_expiration) + require(Chain.block_height > expiration_height, "WRAPPED_NAMES_NOT_EXPIRED") + function require_can_receive_name(target_nft_id: int, target_owner: address) = switch(get_nft_config(target_nft_id, target_owner)) None => abort("RECEIVING_NAME_NOT_ALLOWED") @@ -630,11 +629,18 @@ main contract AENSWrapping : IAEX141, IAENSWrapping = if(Map.member(token_id, state.approvals)) put(state{ approvals = Map.delete(token_id, state.approvals) }) - stateful function __burn(owner: address, token_id: int) = - put(state{ balances[owner] @owner_balance = owner_balance - 1 + stateful function __burn_single(token_id: int) = + let owner = require_authorized(token_id) + require_expired_if_wrapped(token_id) + __remove_approval(token_id) + put(state{ balances[owner] @balance = balance - 1 , total_supply = state.total_supply - 1 , token_to_owner = Map.delete(token_id, state.token_to_owner) - , owner_to_tokens[owner] @owners_tokens = Set.delete(token_id, owners_tokens) }) + , owner_to_tokens[owner] @owners_tokens = Set.delete(token_id, owners_tokens) + , token_to_name_expiration = Map.delete(token_id, state.token_to_name_expiration) + , token_to_config = Map.delete(token_id, state.token_to_config) + , metadata = Map.delete(token_id, state.metadata) }) + Chain.event(Burn(owner, token_id)) function get_possible_reward(owner: address, owner_cfg : config, expiration_height: int) : int = let delta = expiration_height - Chain.block_height diff --git a/development/smart-contracts/test/aensWrappingTest.js b/development/smart-contracts/test/aensWrappingTest.js index 072c2cb..5248344 100644 --- a/development/smart-contracts/test/aensWrappingTest.js +++ b/development/smart-contracts/test/aensWrappingTest.js @@ -749,6 +749,57 @@ describe('AENSWrapping', () => { assert.equal(extendAllForRewardTx.decodedEvents[0].args[1], otherAccount.address); assert.equal(extendAllForRewardTx.decodedEvents[0].args[2], globalConfig.emergency_reward); }); + it('burn & burn_multiple_nfts', async () => { + // prepare: mint 3 different NFTs + await contract.mint(aeSdk.selectedAddress); + await contract.mint(aeSdk.selectedAddress); + await contract.mint(aeSdk.selectedAddress); + + // checks before burning + let totalSupply = (await contract.total_supply()).decodedResult; + let nftBalance = (await contract.balance(aeSdk.selectedAddress)).decodedResult; + let ownedTokens = (await contract.get_owned_tokens(aeSdk.selectedAddress)).decodedResult; + assert.equal(totalSupply, 3); + assert.equal(nftBalance, 3); + assert.deepEqual(ownedTokens, [1n, 2n, 3n]); + + // burn a single NFT + const burnTx = await contract.burn(2); + + // check Burn event + assert.equal(burnTx.decodedEvents[0].name, 'Burn'); + assert.equal(burnTx.decodedEvents[0].args[0], aeSdk.selectedAddress); + assert.equal(burnTx.decodedEvents[0].args[1], 2); + + // checks after burning a single nft + totalSupply = (await contract.total_supply()).decodedResult; + nftBalance = (await contract.balance(aeSdk.selectedAddress)).decodedResult; + ownedTokens = (await contract.get_owned_tokens(aeSdk.selectedAddress)).decodedResult; + assert.equal(totalSupply, 2); + assert.equal(nftBalance, 2); + assert.deepEqual(ownedTokens, [1n, 3n]); + + const burnMultipleNftsTx = await contract.burn_multiple_nfts([1,3]); + + // check Burn events + assert.equal(burnMultipleNftsTx.decodedEvents[0].name, 'Burn'); + assert.equal(burnMultipleNftsTx.decodedEvents[0].args[0], aeSdk.selectedAddress); + assert.equal(burnMultipleNftsTx.decodedEvents[0].args[1], 3); + assert.equal(burnMultipleNftsTx.decodedEvents[1].name, 'Burn'); + assert.equal(burnMultipleNftsTx.decodedEvents[1].args[0], aeSdk.selectedAddress); + assert.equal(burnMultipleNftsTx.decodedEvents[1].args[1], 1); + + // checks after burning multiple nfts + totalSupply = (await contract.total_supply()).decodedResult; + nftBalance = (await contract.balance(aeSdk.selectedAddress)).decodedResult; + ownedTokens = (await contract.get_owned_tokens(aeSdk.selectedAddress)).decodedResult; + assert.equal(totalSupply, 0); + assert.equal(nftBalance, 0); + assert.deepEqual(ownedTokens, []); + + // TODO test burning with expired names + // blocked by https://github.com/aeternity/aeproject/issues/470 + }); }); }); });