From e630832193bc4e37dd9724188a0e619ddcdf2b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hi=E1=BA=BFu=20Ph=E1=BA=A1m?= Date: Fri, 12 Jan 2024 11:34:01 +0700 Subject: [PATCH] allow burn token with proper flag --- contracts/FeralFileAirdropV1.sol | 51 +++++++++++---- migrations/310_feralfile_airdrop_v1.js | 4 +- test/feralfile_airdrop_v1.js | 89 +++++++++++++++++++++++++- 3 files changed, 126 insertions(+), 18 deletions(-) diff --git a/contracts/FeralFileAirdropV1.sol b/contracts/FeralFileAirdropV1.sol index 8bef8a2..6ed5123 100644 --- a/contracts/FeralFileAirdropV1.sol +++ b/contracts/FeralFileAirdropV1.sol @@ -16,14 +16,27 @@ contract FeralFileAirdropV1 is ERC1155, Authorizable { // Airdropped addresses mapping(address => bool) public airdroppedAddresses; - // already ended flag - bool private _ended; - // Token type Type public tokenType; - constructor(Type tokenType_, string memory tokenURI_) ERC1155(tokenURI_) { + // Burnable flag + bool public burnable; + + // Bridgeable flag + bool public bridgeable; + + // Ended flag + bool private _ended; + + constructor( + Type tokenType_, + string memory tokenURI_, + bool burnable_, + bool bridgeable_ + ) ERC1155(tokenURI_) { tokenType = tokenType_; + burnable = burnable_; + bridgeable = bridgeable_; } // @notice check if the airdrop is ended @@ -42,11 +55,7 @@ contract FeralFileAirdropV1 is ERC1155, Authorizable { /// @param amount_ amount of tokens to mint function mint(uint256 tokenID_, uint256 amount_) external onlyAuthorized { require(!_ended, "FeralFileAirdropV1: already ended"); - require( - (tokenType == Type.Fungible && amount_ > 0) || - (tokenType == Type.NonFungible && amount_ == 1), - "FeralFileAirdropV1: amount mismatch" - ); + _checkProperTokenAmount(amount_); _mint(address(this), tokenID_, amount_, ""); } @@ -58,11 +67,7 @@ contract FeralFileAirdropV1 is ERC1155, Authorizable { address[] calldata to_ ) external onlyAuthorized { require(!_ended, "FeralFileAirdropV1: already ended"); - require( - (tokenType == Type.Fungible && to_.length > 0) || - (tokenType == Type.NonFungible && to_.length == 1), - "FeralFileAirdropV1: amount mismatch" - ); + _checkProperTokenAmount(to_.length); // to_ length is the amount of tokens to airdrop uint256[] memory _tokenIDs = new uint256[](1); _tokenIDs[0] = tokenID_; @@ -94,6 +99,24 @@ contract FeralFileAirdropV1 is ERC1155, Authorizable { _burn(address(this), tokenID_, balanceOf(address(this), tokenID_)); } + /// @notice burn tokens + /// @param tokenID_ token ID + /// @param amount_ amount of tokens to burn + function burn(uint256 tokenID_, uint256 amount_) external { + require(burnable, "FeralFileAirdropV1: not burnable"); + _checkProperTokenAmount(amount_); + _burn(_msgSender(), tokenID_, amount_); + } + + /// @notice check proper amount of tokens + function _checkProperTokenAmount(uint256 amount_) internal view { + require( + (tokenType == Type.Fungible && amount_ > 0) || + (tokenType == Type.NonFungible && amount_ == 1), + "FeralFileAirdropV1: amount mismatch" + ); + } + function onERC1155Received( address, address from_, diff --git a/migrations/310_feralfile_airdrop_v1.js b/migrations/310_feralfile_airdrop_v1.js index b9b844a..3d95202 100644 --- a/migrations/310_feralfile_airdrop_v1.js +++ b/migrations/310_feralfile_airdrop_v1.js @@ -9,6 +9,8 @@ module.exports = function (deployer) { argv.token_uri || "https://ipfs.bitmark.com/ipfs/QmNVdQSp1AvZonLwHzTbbZDPLgbpty15RMQrbPEWd4ooTU/{id}"; let type = argv.type || 0; + let burnable = argv.burnable || true; + let bridgeable = argv.bridgeable || true; - deployer.deploy(FeralFileAirdropV1, type, token_uri); + deployer.deploy(FeralFileAirdropV1, type, token_uri, burnable, bridgeable); }; diff --git a/test/feralfile_airdrop_v1.js b/test/feralfile_airdrop_v1.js index 35033a5..8e9051f 100644 --- a/test/feralfile_airdrop_v1.js +++ b/test/feralfile_airdrop_v1.js @@ -15,17 +15,23 @@ contract("FeralFileAirdropV1", async (accounts) => { // To isolate the test cases, we create multiple pairs of contracts // for each token type. The index of the contract pair will be used // as index of test cases. - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 7; i++) { + let burnable = i == 6 ? false : true; + let bridgeable = true; let fungibleContract = await FeralFileAirdropV1.new( TOKEN_TYPE_FUNGIBLE, - TOKEN_URI + TOKEN_URI, + burnable, + bridgeable ); await fungibleContract.addTrustee(this.trustee, { from: this.owner, }); let nonFungibleContract = await FeralFileAirdropV1.new( TOKEN_TYPE_NON_FUNGIBLE, - TOKEN_URI + TOKEN_URI, + burnable, + bridgeable ); await nonFungibleContract.addTrustee(this.trustee, { from: this.owner, @@ -269,4 +275,81 @@ contract("FeralFileAirdropV1", async (accounts) => { assert.equal(afterBalance.toNumber(), 0); } }); + + it("test burn with burnable=true", async () => { + let contracts = this.contracts[5]; // burnable=true + for (let i = 0; i < contracts.length; i++) { + let contract = contracts[i]; + + // mint token to prepare for below tests + let correctAmount = i == 0 ? 10 : 1; + await contract.mint(TOKEN_ID, correctAmount, { + from: this.trustee, + }); + + recipient = accounts[0]; + // test insufficient balance + try { + await contract.burn(TOKEN_ID, correctAmount, { + from: recipient, + }); + } catch (e) { + assert.equal(e.reason, "ERC1155: burn amount exceeds balance"); + } + + // airdrop to recipient + await contract.airdrop(TOKEN_ID, [recipient], { + from: this.trustee, + }); + + // test burn amount mismatch + wrongAmount = i == 0 ? 0 : 2; + try { + await contract.burn(TOKEN_ID, wrongAmount, { from: recipient }); + } catch (e) { + assert.equal(e.reason, "FeralFileAirdropV1: amount mismatch"); + } + + // test burn successfully + let beforeBalance = await contract.balanceOf(recipient, TOKEN_ID); + assert.equal(beforeBalance.toNumber(), 1); + + await contract.burn(TOKEN_ID, 1, { from: this.owner }); + + let afterBalance = await contract.balanceOf(recipient, TOKEN_ID); + assert.equal(afterBalance.toNumber(), 0); + } + }); + + it("test burn with burnable=false", async () => { + let contracts = this.contracts[6]; // burnable=false + for (let i = 0; i < contracts.length; i++) { + let contract = contracts[i]; + + // make sure burnable=false + assert.equal(await contract.burnable(), false); + + // mint token to prepare for below tests + let correctAmount = i == 0 ? 10 : 1; + await contract.mint(TOKEN_ID, correctAmount, { + from: this.trustee, + }); + + burner = accounts[0]; + // airdrop to burner + await contract.airdrop(TOKEN_ID, [burner], { + from: this.trustee, + }); + + // make sure burner has sufficient balance + assert.equal(await contract.balanceOf(burner, TOKEN_ID), 1); + + // test burn fails due to the burnable=false + try { + await contract.burn(TOKEN_ID, 1, { from: burner }); + } catch (e) { + assert.equal(e.reason, "FeralFileAirdropV1: not burnable"); + } + } + }); });