Browse Source

ERC721 pausable token (#1154)

* ERC721 pausable token

* Reuse of ERC721 Basic behavior for Pausable, split view checks in paused state & style fixes

* [~] paused token behavior
Roman Exempliarov 7 years ago
parent
commit
9e6307f34b

+ 22 - 0
contracts/mocks/ERC721PausableTokenMock.sol

@@ -0,0 +1,22 @@
+pragma solidity ^0.4.24;
+
+import "../token/ERC721/ERC721PausableToken.sol";
+
+
+/**
+ * @title ERC721PausableTokenMock
+ * This mock just provides a public mint, burn and exists functions for testing purposes
+ */
+contract ERC721PausableTokenMock is ERC721PausableToken {
+  function mint(address _to, uint256 _tokenId) public {
+    super._mint(_to, _tokenId);
+  }
+
+  function burn(uint256 _tokenId) public {
+    super._burn(ownerOf(_tokenId), _tokenId);
+  }
+
+  function exists(uint256 _tokenId) public view returns (bool) {
+    return super._exists(_tokenId);
+  }
+}

+ 42 - 0
contracts/token/ERC721/ERC721PausableToken.sol

@@ -0,0 +1,42 @@
+pragma solidity ^0.4.24;
+
+import "./ERC721BasicToken.sol";
+import "../../lifecycle/Pausable.sol";
+
+
+/**
+ * @title ERC721 Non-Fungible Pausable token
+ * @dev ERC721BasicToken modified with pausable transfers.
+ **/
+contract ERC721PausableToken is ERC721BasicToken, Pausable {
+  function approve(
+    address _to,
+    uint256 _tokenId
+  )
+    public
+    whenNotPaused
+  {
+    super.approve(_to, _tokenId);
+  }
+
+  function setApprovalForAll(
+    address _to,
+    bool _approved
+  )
+    public
+    whenNotPaused
+  {
+    super.setApprovalForAll(_to, _approved);
+  }
+
+  function transferFrom(
+    address _from,
+    address _to,
+    uint256 _tokenId
+  )
+    public
+    whenNotPaused
+  {
+    super.transferFrom(_from, _to, _tokenId);
+  }
+}

+ 36 - 0
test/token/ERC721/ERC721PausableToken.test.js

@@ -0,0 +1,36 @@
+const { shouldBehaveLikeERC721PausedToken } = require('./ERC721PausedToken.behavior');
+const { shouldBehaveLikeERC721BasicToken } = require('./ERC721BasicToken.behavior');
+
+const BigNumber = web3.BigNumber;
+const ERC721PausableToken = artifacts.require('ERC721PausableTokenMock.sol');
+
+require('chai')
+  .use(require('chai-bignumber')(BigNumber))
+  .should();
+
+contract('ERC721PausableToken', function ([_, owner, recipient, operator, ...otherAccounts]) {
+  beforeEach(async function () {
+    this.token = await ERC721PausableToken.new({ from: owner });
+  });
+
+  context('when token is paused', function () {
+    beforeEach(async function () {
+      await this.token.pause({ from: owner });
+    });
+
+    shouldBehaveLikeERC721PausedToken(owner, [...otherAccounts]);
+  });
+
+  context('when token is not paused yet', function () {
+    shouldBehaveLikeERC721BasicToken([owner, ...otherAccounts]);
+  });
+
+  context('when token is paused and then unpaused', function () {
+    beforeEach(async function () {
+      await this.token.pause({ from: owner });
+      await this.token.unpause({ from: owner });
+    });
+
+    shouldBehaveLikeERC721BasicToken([owner, ...otherAccounts]);
+  });
+});

+ 88 - 0
test/token/ERC721/ERC721PausedToken.behavior.js

@@ -0,0 +1,88 @@
+const { assertRevert } = require('../../helpers/assertRevert');
+const { sendTransaction } = require('../../helpers/sendTransaction');
+
+const BigNumber = web3.BigNumber;
+
+require('chai')
+  .use(require('chai-bignumber')(BigNumber))
+  .should();
+
+function shouldBehaveLikeERC721PausedToken (owner, [recipient, operator]) {
+  const firstTokenId = 1;
+  const mintedTokens = 1;
+  const mockData = '0x42';
+  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
+
+  describe('like a paused ERC721Token', function () {
+    beforeEach(async function () {
+      await this.token.mint(owner, firstTokenId, { from: owner });
+    });
+
+    it('reverts when trying to approve', async function () {
+      await assertRevert(this.token.approve(recipient, firstTokenId, { from: owner }));
+    });
+
+    it('reverts when trying to setApprovalForAll', async function () {
+      await assertRevert(this.token.setApprovalForAll(operator, true, { from: owner }));
+    });
+
+    it('reverts when trying to transferFrom', async function () {
+      await assertRevert(this.token.transferFrom(owner, recipient, firstTokenId, { from: owner }));
+    });
+
+    it('reverts when trying to safeTransferFrom', async function () {
+      await assertRevert(this.token.safeTransferFrom(owner, recipient, firstTokenId, { from: owner }));
+    });
+
+    it('reverts when trying to safeTransferFrom with data', async function () {
+      await assertRevert(
+        sendTransaction(
+          this.token,
+          'safeTransferFrom',
+          'address,address,uint256,bytes',
+          [owner, recipient, firstTokenId, mockData],
+          { from: owner }
+        )
+      );
+    });
+
+    describe('getApproved', function () {
+      it('returns approved address', async function () {
+        const approvedAccount = await this.token.getApproved(firstTokenId);
+        approvedAccount.should.be.equal(ZERO_ADDRESS);
+      });
+    });
+
+    describe('balanceOf', function () {
+      it('returns the amount of tokens owned by the given address', async function () {
+        const balance = await this.token.balanceOf(owner);
+        balance.should.be.bignumber.equal(mintedTokens);
+      });
+    });
+
+    describe('ownerOf', function () {
+      it('returns the amount of tokens owned by the given address', async function () {
+        const ownerOfToken = await this.token.ownerOf(firstTokenId);
+        ownerOfToken.should.be.equal(owner);
+      });
+    });
+
+    describe('exists', function () {
+      it('should return token existance', async function () {
+        const result = await this.token.exists(firstTokenId);
+        result.should.eq(true);
+      });
+    });
+
+    describe('isApprovedForAll', function () {
+      it('returns the approval of the operator', async function () {
+        const isApproved = await this.token.isApprovedForAll(owner, operator);
+        isApproved.should.eq(false);
+      });
+    });
+  });
+}
+
+module.exports = {
+  shouldBehaveLikeERC721PausedToken,
+};