diff --git a/development/smart-contracts/contracts/AENSWrapping.aes b/development/smart-contracts/contracts/AENSWrapping.aes index 4a05dae..a403cdd 100644 --- a/development/smart-contracts/contracts/AENSWrapping.aes +++ b/development/smart-contracts/contracts/AENSWrapping.aes @@ -67,6 +67,8 @@ main contract AENSWrapping : IAEX141, IAENSWrapping = | NameExtend(string, int, int, address) // name, nft_id_old, nft_id_new | NameTransfer(string, int, int) + // name, nft_id + | NameRevoke(string, int) // nft_id, caller, reward | Reward(int, address, int) @@ -304,21 +306,25 @@ main contract AENSWrapping : IAEX141, IAENSWrapping = /// @param nft_id the id of the NFT where the AENS name is wrapped into /// @param name the AENS name to revoke stateful entrypoint revoke_single(nft_id: int, name: string) = - // TODO - () + require_authorized(nft_id) + require_not_expired(nft_id) + __revoke(nft_id, name) /// @notice revokes multiple AENS names wrapped in the NFT, removes metadata /// @param nft_id the id of the NFT where the AENS name is wrapped into /// @param names the AENS names to revoke stateful entrypoint revoke_multiple(nft_id: int, names: Set.set(string)) = - // TODO - () + require_authorized(nft_id) + require_not_expired(nft_id) + List.foreach(Set.to_list(names), (n) => __revoke(nft_id, n)) /// @notice revokes all AENS names wrapped in the NFT, removes metadata and burns the NFT /// @param nft_id the id of the NFT where the AENS name is wrapped into stateful entrypoint revoke_all(nft_id: int) = - // TODO - () + require_authorized(nft_id) + require_not_expired(nft_id) + let nft_metadata_map = require_names_wrapped(nft_id) + List.foreach(Map.to_list(nft_metadata_map), (val) => __revoke(nft_id, Pair.fst(val))) /// @notice transfers a single AENS name to another NFT by updating metadata of both NFTs, updates expiry of name to match expiry of already wrapped names /// @param nft_id_old the id of the NFT that currently wraps the AENS name @@ -493,6 +499,13 @@ main contract AENSWrapping : IAEX141, IAENSWrapping = stateful entrypoint transfer_multiple_nfts(recipient: address, nft_ids: Set.set(int), data: option(string)) = List.foreach(Set.to_list(nft_ids), (id) => __transfer_single_nft(recipient, id, data)) + stateful function __revoke(nft_id: int, name: string) = + let nft_metadata_map = require_name_wrapped(nft_id, name) + AENS.revoke(Contract.address, name) + put(state{ metadata[nft_id] @nft_metadata = MetadataMap(Map.delete(name, nft_metadata_map)) }) + put(state{ name_to_token @ val = Map.delete(name, val) }) + Chain.event(NameRevoke(name, nft_id)) + stateful function __transfer_single_nft(to: address, token_id: int, data: option(string)) = let from = require_authorized(token_id) require(from != to, "SENDER_MUST_NOT_BE_RECEIVER") diff --git a/development/smart-contracts/contracts/interfaces/IAENSWrapping.aes b/development/smart-contracts/contracts/interfaces/IAENSWrapping.aes index 828986d..84672dc 100644 --- a/development/smart-contracts/contracts/interfaces/IAENSWrapping.aes +++ b/development/smart-contracts/contracts/interfaces/IAENSWrapping.aes @@ -11,6 +11,8 @@ contract interface IAENSWrapping = | NameExtend(string, int, int, address) // name, nft_id_old, nft_id_new | NameTransfer(string, int, int) + // name, nft_id + | NameRevoke(string, int) // nft_id, caller, reward | Reward(int, address, int) diff --git a/development/smart-contracts/test/aensWrappingTest.js b/development/smart-contracts/test/aensWrappingTest.js index 4485cd5..061974e 100644 --- a/development/smart-contracts/test/aensWrappingTest.js +++ b/development/smart-contracts/test/aensWrappingTest.js @@ -739,6 +739,7 @@ describe('AENSWrapping', () => { assert.equal(extendAllForRewardTx.decodedEvents[0].args[1], otherAccount.address); assert.equal(extendAllForRewardTx.decodedEvents[0].args[2], globalConfig.reward); }); + it('extend_all_for_reward (emergency reward)', async () => { // prepare: claim and wrap names await claimNames(aensNames); @@ -792,6 +793,7 @@ 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); @@ -808,6 +810,7 @@ describe('AENSWrapping', () => { // burn a single NFT const burnTx = await contract.burn(2); + console.log(`Gas used (burn): ${burnTx.result.gasUsed}`); // check Burn event assert.equal(burnTx.decodedEvents[0].name, 'Burn'); @@ -823,6 +826,7 @@ describe('AENSWrapping', () => { assert.deepEqual(ownedTokens, [1n, 3n]); const burnMultipleNftsTx = await contract.burn_multiple_nfts([1,3]); + console.log(`Gas used (burn_multiple_nfts with 2 nfts): ${burnMultipleNftsTx.result.gasUsed}`); // check Burn events assert.equal(burnMultipleNftsTx.decodedEvents[0].name, 'Burn'); @@ -843,6 +847,110 @@ describe('AENSWrapping', () => { // TODO test burning with expired names // blocked by https://github.com/aeternity/aeproject/issues/470 }); + + it('revoke_single', async () => { + // prepare: claim and wrap names + await claimNames(aensNames); + const namesDelegationSigs = await getDelegationSignatures(aensNames, contractId); + await contract.wrap_and_mint(namesDelegationSigs); + + // pre revocation checks + const expirationHeight = (await contract.get_expiration_by_nft_id(1)).decodedResult; + let nftDataOne = (await contract.get_nft_data(1)).decodedResult; + assert.deepEqual(nftDataOne, {id: 1n, owner: aeSdk.selectedAddress, owner_config: undefined, names: aensNames, expiration_height: expirationHeight}); + await expectNftMetadataMap(1, getExpectedNftMetadataMap(aensNames)); + + const revokeSingleTx = await contract.revoke_single(1, aensNames[0]); + console.log(`Gas used (revoke_single): ${revokeSingleTx.result.gasUsed}`); + + // check NameRevoke event + assert.equal(revokeSingleTx.decodedEvents[0].name, 'NameRevoke'); + assert.equal(revokeSingleTx.decodedEvents[0].args[0], aensNames[0]); + assert.equal(revokeSingleTx.decodedEvents[0].args[1], 1); + + // after revocation checks + nftDataOne = (await contract.get_nft_data(1)).decodedResult; + assert.deepEqual(nftDataOne, {id: 1n, owner: aeSdk.selectedAddress, owner_config: undefined, names: aensNames.slice(1), expiration_height: expirationHeight}); + await expectNftMetadataMap(1, getExpectedNftMetadataMap(aensNames.slice(1))); + try { + await aeSdk.aensQuery(aensNames[0]); + } catch(e) { + assert.equal(e.statusCode, 404); + assert.equal(e.details.reason, "Name revoked"); + } + }); + + it('revoke_multiple', async () => { + // prepare: claim and wrap names + await claimNames(aensNames); + const namesDelegationSigs = await getDelegationSignatures(aensNames, contractId); + await contract.wrap_and_mint(namesDelegationSigs); + + // pre revocation checks + const expirationHeight = (await contract.get_expiration_by_nft_id(1)).decodedResult; + let nftDataOne = (await contract.get_nft_data(1)).decodedResult; + assert.deepEqual(nftDataOne, {id: 1n, owner: aeSdk.selectedAddress, owner_config: undefined, names: aensNames, expiration_height: expirationHeight}); + await expectNftMetadataMap(1, getExpectedNftMetadataMap(aensNames)); + + const revokeMultipleTx = await contract.revoke_multiple(1, aensNames.slice(1)); + console.log(`Gas used (revoke_multiple) with ${aensNames.slice(1).length} names: ${revokeMultipleTx.result.gasUsed}`); + + // check NameRevoke events + for(let i=0; i { + // prepare: claim and wrap names + await claimNames(aensNames); + const namesDelegationSigs = await getDelegationSignatures(aensNames, contractId); + await contract.wrap_and_mint(namesDelegationSigs); + + // pre revocation checks + const expirationHeight = (await contract.get_expiration_by_nft_id(1)).decodedResult; + let nftDataOne = (await contract.get_nft_data(1)).decodedResult; + assert.deepEqual(nftDataOne, {id: 1n, owner: aeSdk.selectedAddress, owner_config: undefined, names: aensNames, expiration_height: expirationHeight}); + await expectNftMetadataMap(1, getExpectedNftMetadataMap(aensNames)); + + const revokeAllTx = await contract.revoke_all(1); + console.log(`Gas used (revoke_all) with ${aensNames.length} names: ${revokeAllTx.result.gasUsed}`); + + // check NameRevoke events + for(let i=0; i