Selaa lähdekoodia

Added message string for require() (#1704)

* Error handling in ERC20 and ERC721

* Added message string for require.

* Fixed solhint errors.

* Updated PR as per issue #1709

* changes as per #1709 and openzeppelin forum.

* Changes in require statement

* Changes in require statement

* build pipeline fix

* Changes as per @nventuro's comment.

* Update revert reason strings.

* Fianal update of revert reason strings.

* WIP: Updating reason strings in test cases

* WIP: Added changes to ERC20 and ERC721

* Fixes linting errors in *.tes.js files

* Achieved 100% code coverage

* Updated the test cases with shouldFail.reverting.withMessage()

* Fix package-lock.

* address review comments

* fix linter issues

* fix remaining revert reasons
Balaji Pachai 6 vuotta sitten
vanhempi
sitoutus
3682c6575c
93 muutettua tiedostoa jossa 769 lisäystä ja 423 poistoa
  1. 3 5
      contracts/access/Roles.sol
  2. 1 1
      contracts/access/roles/CapperRole.sol
  3. 1 1
      contracts/access/roles/MinterRole.sol
  4. 1 1
      contracts/access/roles/PauserRole.sol
  5. 1 1
      contracts/access/roles/SignerRole.sol
  6. 1 1
      contracts/access/roles/WhitelistAdminRole.sol
  7. 1 1
      contracts/access/roles/WhitelistedRole.sol
  8. 5 5
      contracts/crowdsale/Crowdsale.sol
  9. 2 2
      contracts/crowdsale/distribution/FinalizableCrowdsale.sol
  10. 2 2
      contracts/crowdsale/distribution/PostDeliveryCrowdsale.sol
  11. 3 3
      contracts/crowdsale/distribution/RefundableCrowdsale.sol
  12. 2 2
      contracts/crowdsale/distribution/RefundablePostDeliveryCrowdsale.sol
  13. 1 1
      contracts/crowdsale/emission/AllowanceCrowdsale.sol
  14. 4 1
      contracts/crowdsale/emission/MintedCrowdsale.sol
  15. 4 3
      contracts/crowdsale/price/IncreasingPriceCrowdsale.sol
  16. 2 2
      contracts/crowdsale/validation/CappedCrowdsale.sol
  17. 2 1
      contracts/crowdsale/validation/IndividuallyCappedCrowdsale.sol
  18. 7 5
      contracts/crowdsale/validation/TimedCrowdsale.sol
  19. 1 1
      contracts/crowdsale/validation/WhitelistCrowdsale.sol
  20. 6 5
      contracts/drafts/ERC20Migrator.sol
  21. 3 2
      contracts/drafts/ERC20Snapshot.sol
  22. 6 4
      contracts/drafts/SignatureBouncer.sol
  23. 6 6
      contracts/drafts/SignedSafeMath.sol
  24. 9 7
      contracts/drafts/TokenVesting.sol
  25. 1 1
      contracts/examples/SampleCrowdsale.sol
  26. 1 1
      contracts/introspection/ERC165.sol
  27. 2 2
      contracts/lifecycle/Pausable.sol
  28. 5 5
      contracts/math/SafeMath.sol
  29. 1 1
      contracts/mocks/ERC165/ERC165InterfacesSupported.sol
  30. 1 1
      contracts/mocks/ERC721ReceiverMock.sol
  31. 1 1
      contracts/mocks/ReentrancyAttack.sol
  32. 1 1
      contracts/mocks/ReentrancyMock.sol
  33. 1 1
      contracts/mocks/SafeERC20Helper.sol
  34. 2 2
      contracts/ownership/Ownable.sol
  35. 2 2
      contracts/ownership/Secondary.sol
  36. 8 7
      contracts/payment/PaymentSplitter.sol
  37. 1 1
      contracts/payment/escrow/ConditionalEscrow.sol
  38. 5 5
      contracts/payment/escrow/RefundEscrow.sol
  39. 5 5
      contracts/token/ERC20/ERC20.sol
  40. 2 2
      contracts/token/ERC20/ERC20Capped.sol
  41. 9 5
      contracts/token/ERC20/SafeERC20.sol
  42. 3 3
      contracts/token/ERC20/TokenTimelock.sol
  43. 22 13
      contracts/token/ERC721/ERC721.sol
  44. 2 1
      contracts/token/ERC721/ERC721Burnable.sol
  45. 2 2
      contracts/token/ERC721/ERC721Enumerable.sol
  46. 2 2
      contracts/token/ERC721/ERC721Metadata.sol
  47. 1 1
      contracts/utils/ReentrancyGuard.sol
  48. 5 5
      test/access/Roles.test.js
  49. 21 7
      test/behaviors/access/roles/PublicRole.behavior.js
  50. 3 1
      test/crowdsale/AllowanceCrowdsale.test.js
  51. 5 3
      test/crowdsale/CappedCrowdsale.test.js
  52. 14 12
      test/crowdsale/Crowdsale.test.js
  53. 6 2
      test/crowdsale/FinalizableCrowdsale.test.js
  54. 9 7
      test/crowdsale/IncreasingPriceCrowdsale.test.js
  55. 18 6
      test/crowdsale/IndividuallyCappedCrowdsale.test.js
  56. 6 2
      test/crowdsale/PausableCrowdsale.test.js
  57. 6 2
      test/crowdsale/PostDeliveryCrowdsale.test.js
  58. 12 5
      test/crowdsale/RefundableCrowdsale.test.js
  59. 12 4
      test/crowdsale/RefundablePostDeliveryCrowdsale.test.js
  60. 24 14
      test/crowdsale/TimedCrowdsale.test.js
  61. 6 2
      test/crowdsale/WhitelistCrowdsale.test.js
  62. 3 2
      test/cryptography/ECDSA.test.js
  63. 1 1
      test/drafts/Counters.test.js
  64. 3 2
      test/drafts/ERC1820Implementer.test.js
  65. 21 7
      test/drafts/ERC20Migrator.test.js
  66. 4 4
      test/drafts/ERC20Snapshot.test.js
  67. 30 24
      test/drafts/SignatureBouncer.test.js
  68. 11 11
      test/drafts/SignedSafeMath.test.js
  69. 20 11
      test/drafts/TokenVesting.test.js
  70. 11 7
      test/examples/SampleCrowdsale.test.js
  71. 1 1
      test/introspection/ERC165.test.js
  72. 15 7
      test/lifecycle/Pausable.test.js
  73. 8 8
      test/math/SafeMath.test.js
  74. 12 3
      test/ownership/Ownable.behavior.js
  75. 12 4
      test/ownership/Secondary.test.js
  76. 22 8
      test/payment/PaymentSplitter.test.js
  77. 3 1
      test/payment/escrow/ConditionalEscrow.test.js
  78. 6 2
      test/payment/escrow/Escrow.behavior.js
  79. 38 14
      test/payment/escrow/RefundEscrow.test.js
  80. 56 23
      test/token/ERC20/ERC20.test.js
  81. 2 2
      test/token/ERC20/ERC20Capped.test.js
  82. 23 9
      test/token/ERC20/ERC20Pausable.test.js
  83. 20 9
      test/token/ERC20/SafeERC20.test.js
  84. 4 4
      test/token/ERC20/TokenTimelock.test.js
  85. 9 3
      test/token/ERC20/behaviors/ERC20Burnable.behavior.js
  86. 2 2
      test/token/ERC20/behaviors/ERC20Capped.behavior.js
  87. 3 1
      test/token/ERC20/behaviors/ERC20Mintable.behavior.js
  88. 40 21
      test/token/ERC721/ERC721.behavior.js
  89. 27 10
      test/token/ERC721/ERC721.test.js
  90. 21 7
      test/token/ERC721/ERC721Full.test.js
  91. 16 6
      test/token/ERC721/ERC721MintBurn.behavior.js
  92. 16 6
      test/token/ERC721/ERC721PausedToken.behavior.js
  93. 8 3
      test/utils/ReentrancyGuard.test.js

+ 3 - 5
contracts/access/Roles.sol

@@ -13,8 +13,7 @@ library Roles {
      * @dev Give an account access to this role.
      */
     function add(Role storage role, address account) internal {
-        require(!has(role, account));
-
+        require(!has(role, account), "Roles: account already has role");
         role.bearer[account] = true;
     }
 
@@ -22,8 +21,7 @@ library Roles {
      * @dev Remove an account's access to this role.
      */
     function remove(Role storage role, address account) internal {
-        require(has(role, account));
-
+        require(has(role, account), "Roles: account does not have role");
         role.bearer[account] = false;
     }
 
@@ -32,7 +30,7 @@ library Roles {
      * @return bool
      */
     function has(Role storage role, address account) internal view returns (bool) {
-        require(account != address(0));
+        require(account != address(0), "Roles: account is the zero address");
         return role.bearer[account];
     }
 }

+ 1 - 1
contracts/access/roles/CapperRole.sol

@@ -15,7 +15,7 @@ contract CapperRole {
     }
 
     modifier onlyCapper() {
-        require(isCapper(msg.sender));
+        require(isCapper(msg.sender), "CapperRole: caller does not have the Capper role");
         _;
     }
 

+ 1 - 1
contracts/access/roles/MinterRole.sol

@@ -15,7 +15,7 @@ contract MinterRole {
     }
 
     modifier onlyMinter() {
-        require(isMinter(msg.sender));
+        require(isMinter(msg.sender), "MinterRole: caller does not have the Minter role");
         _;
     }
 

+ 1 - 1
contracts/access/roles/PauserRole.sol

@@ -15,7 +15,7 @@ contract PauserRole {
     }
 
     modifier onlyPauser() {
-        require(isPauser(msg.sender));
+        require(isPauser(msg.sender), "PauserRole: caller does not have the Pauser role");
         _;
     }
 

+ 1 - 1
contracts/access/roles/SignerRole.sol

@@ -15,7 +15,7 @@ contract SignerRole {
     }
 
     modifier onlySigner() {
-        require(isSigner(msg.sender));
+        require(isSigner(msg.sender), "SignerRole: caller does not have the Signer role");
         _;
     }
 

+ 1 - 1
contracts/access/roles/WhitelistAdminRole.sol

@@ -19,7 +19,7 @@ contract WhitelistAdminRole {
     }
 
     modifier onlyWhitelistAdmin() {
-        require(isWhitelistAdmin(msg.sender));
+        require(isWhitelistAdmin(msg.sender), "WhitelistAdminRole: caller does not have the WhitelistAdmin role");
         _;
     }
 

+ 1 - 1
contracts/access/roles/WhitelistedRole.sol

@@ -18,7 +18,7 @@ contract WhitelistedRole is WhitelistAdminRole {
     Roles.Role private _whitelisteds;
 
     modifier onlyWhitelisted() {
-        require(isWhitelisted(msg.sender));
+        require(isWhitelisted(msg.sender), "WhitelistedRole: caller does not have the Whitelisted role");
         _;
     }
 

+ 5 - 5
contracts/crowdsale/Crowdsale.sol

@@ -54,9 +54,9 @@ contract Crowdsale is ReentrancyGuard {
      * @param token Address of the token being sold
      */
     constructor (uint256 rate, address payable wallet, IERC20 token) public {
-        require(rate > 0);
-        require(wallet != address(0));
-        require(address(token) != address(0));
+        require(rate > 0, "Crowdsale: rate is 0");
+        require(wallet != address(0), "Crowdsale: wallet is the zero address");
+        require(address(token) != address(0), "Crowdsale: token is the zero address");
 
         _rate = rate;
         _wallet = wallet;
@@ -136,8 +136,8 @@ contract Crowdsale is ReentrancyGuard {
      * @param weiAmount Value in wei involved in the purchase
      */
     function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
-        require(beneficiary != address(0));
-        require(weiAmount != 0);
+        require(beneficiary != address(0), "Crowdsale: beneficiary is the zero address");
+        require(weiAmount != 0, "Crowdsale: weiAmount is 0");
     }
 
     /**

+ 2 - 2
contracts/crowdsale/distribution/FinalizableCrowdsale.sol

@@ -31,8 +31,8 @@ contract FinalizableCrowdsale is TimedCrowdsale {
      * work. Calls the contract's finalization function.
      */
     function finalize() public {
-        require(!_finalized);
-        require(hasClosed());
+        require(!_finalized, "FinalizableCrowdsale: already finalized");
+        require(hasClosed(), "FinalizableCrowdsale: not closed");
 
         _finalized = true;
 

+ 2 - 2
contracts/crowdsale/distribution/PostDeliveryCrowdsale.sol

@@ -24,9 +24,9 @@ contract PostDeliveryCrowdsale is TimedCrowdsale {
      * @param beneficiary Whose tokens will be withdrawn.
      */
     function withdrawTokens(address beneficiary) public {
-        require(hasClosed());
+        require(hasClosed(), "PostDeliveryCrowdsale: not closed");
         uint256 amount = _balances[beneficiary];
-        require(amount > 0);
+        require(amount > 0, "PostDeliveryCrowdsale: beneficiary is not due any tokens");
 
         _balances[beneficiary] = 0;
         _vault.transfer(token(), beneficiary, amount);

+ 3 - 3
contracts/crowdsale/distribution/RefundableCrowdsale.sol

@@ -28,7 +28,7 @@ contract RefundableCrowdsale is FinalizableCrowdsale {
      * @param goal Funding goal
      */
     constructor (uint256 goal) public {
-        require(goal > 0);
+        require(goal > 0, "RefundableCrowdsale: goal is 0");
         _escrow = new RefundEscrow(wallet());
         _goal = goal;
     }
@@ -45,8 +45,8 @@ contract RefundableCrowdsale is FinalizableCrowdsale {
      * @param refundee Whose refund will be claimed.
      */
     function claimRefund(address payable refundee) public {
-        require(finalized());
-        require(!goalReached());
+        require(finalized(), "RefundableCrowdsale: not finalized");
+        require(!goalReached(), "RefundableCrowdsale: goal reached");
 
         _escrow.withdraw(refundee);
     }

+ 2 - 2
contracts/crowdsale/distribution/RefundablePostDeliveryCrowdsale.sol

@@ -12,8 +12,8 @@ import "./PostDeliveryCrowdsale.sol";
  */
 contract RefundablePostDeliveryCrowdsale is RefundableCrowdsale, PostDeliveryCrowdsale {
     function withdrawTokens(address beneficiary) public {
-        require(finalized());
-        require(goalReached());
+        require(finalized(), "RefundablePostDeliveryCrowdsale: not finalized");
+        require(goalReached(), "RefundablePostDeliveryCrowdsale: goal not reached");
 
         super.withdrawTokens(beneficiary);
     }

+ 1 - 1
contracts/crowdsale/emission/AllowanceCrowdsale.sol

@@ -21,7 +21,7 @@ contract AllowanceCrowdsale is Crowdsale {
      * @param tokenWallet Address holding the tokens, which has approved allowance to the crowdsale.
      */
     constructor (address tokenWallet) public {
-        require(tokenWallet != address(0));
+        require(tokenWallet != address(0), "AllowanceCrowdsale: token wallet is the zero address");
         _tokenWallet = tokenWallet;
     }
 

+ 4 - 1
contracts/crowdsale/emission/MintedCrowdsale.sol

@@ -16,6 +16,9 @@ contract MintedCrowdsale is Crowdsale {
      */
     function _deliverTokens(address beneficiary, uint256 tokenAmount) internal {
         // Potentially dangerous assumption about the type of the token.
-        require(ERC20Mintable(address(token())).mint(beneficiary, tokenAmount));
+        require(
+            ERC20Mintable(address(token())).mint(beneficiary, tokenAmount),
+                "MintedCrowdsale: minting failed"
+        );
     }
 }

+ 4 - 3
contracts/crowdsale/price/IncreasingPriceCrowdsale.sol

@@ -21,8 +21,9 @@ contract IncreasingPriceCrowdsale is TimedCrowdsale {
      * @param finalRate Number of tokens a buyer gets per wei at the end of the crowdsale
      */
     constructor (uint256 initialRate, uint256 finalRate) public {
-        require(finalRate > 0);
-        require(initialRate > finalRate);
+        require(finalRate > 0, "IncreasingPriceCrowdsale: final rate is 0");
+        // solhint-disable-next-line max-line-length
+        require(initialRate > finalRate, "IncreasingPriceCrowdsale: initial rate is not greater than final rate");
         _initialRate = initialRate;
         _finalRate = finalRate;
     }
@@ -32,7 +33,7 @@ contract IncreasingPriceCrowdsale is TimedCrowdsale {
      * all calls to it are a mistake.
      */
     function rate() public view returns (uint256) {
-        revert();
+        revert("IncreasingPriceCrowdsale: rate() called");
     }
 
     /**

+ 2 - 2
contracts/crowdsale/validation/CappedCrowdsale.sol

@@ -17,7 +17,7 @@ contract CappedCrowdsale is Crowdsale {
      * @param cap Max amount of wei to be contributed
      */
     constructor (uint256 cap) public {
-        require(cap > 0);
+        require(cap > 0, "CappedCrowdsale: cap is 0");
         _cap = cap;
     }
 
@@ -43,6 +43,6 @@ contract CappedCrowdsale is Crowdsale {
      */
     function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
         super._preValidatePurchase(beneficiary, weiAmount);
-        require(weiRaised().add(weiAmount) <= _cap);
+        require(weiRaised().add(weiAmount) <= _cap, "CappedCrowdsale: cap exceeded");
     }
 }

+ 2 - 1
contracts/crowdsale/validation/IndividuallyCappedCrowdsale.sol

@@ -48,7 +48,8 @@ contract IndividuallyCappedCrowdsale is Crowdsale, CapperRole {
      */
     function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
         super._preValidatePurchase(beneficiary, weiAmount);
-        require(_contributions[beneficiary].add(weiAmount) <= _caps[beneficiary]);
+        // solhint-disable-next-line max-line-length
+        require(_contributions[beneficiary].add(weiAmount) <= _caps[beneficiary], "IndividuallyCappedCrowdsale: beneficiary's cap exceeded");
     }
 
     /**

+ 7 - 5
contracts/crowdsale/validation/TimedCrowdsale.sol

@@ -24,7 +24,7 @@ contract TimedCrowdsale is Crowdsale {
      * @dev Reverts if not in crowdsale time range.
      */
     modifier onlyWhileOpen {
-        require(isOpen());
+        require(isOpen(), "TimedCrowdsale: not open");
         _;
     }
 
@@ -35,8 +35,9 @@ contract TimedCrowdsale is Crowdsale {
      */
     constructor (uint256 openingTime, uint256 closingTime) public {
         // solhint-disable-next-line not-rely-on-time
-        require(openingTime >= block.timestamp);
-        require(closingTime > openingTime);
+        require(openingTime >= block.timestamp, "TimedCrowdsale: opening time is before current time");
+        // solhint-disable-next-line max-line-length
+        require(closingTime > openingTime, "TimedCrowdsale: opening time is not before closing time");
 
         _openingTime = openingTime;
         _closingTime = closingTime;
@@ -87,8 +88,9 @@ contract TimedCrowdsale is Crowdsale {
      * @param newClosingTime Crowdsale closing time
      */
     function _extendTime(uint256 newClosingTime) internal {
-        require(!hasClosed());
-        require(newClosingTime > _closingTime);
+        require(!hasClosed(), "TimedCrowdsale: already closed");
+        // solhint-disable-next-line max-line-length
+        require(newClosingTime > _closingTime, "TimedCrowdsale: new closing time is before current closing time");
 
         emit TimedCrowdsaleExtended(_closingTime, newClosingTime);
         _closingTime = newClosingTime;

+ 1 - 1
contracts/crowdsale/validation/WhitelistCrowdsale.sol

@@ -15,7 +15,7 @@ contract WhitelistCrowdsale is WhitelistedRole, Crowdsale {
      * @param _weiAmount Amount of wei contributed
      */
     function _preValidatePurchase(address _beneficiary, uint256 _weiAmount) internal view {
-        require(isWhitelisted(_beneficiary));
+        require(isWhitelisted(_beneficiary), "WhitelistCrowdsale: beneficiary doesn't have the Whitelisted role");
         super._preValidatePurchase(_beneficiary, _weiAmount);
     }
 }

+ 6 - 5
contracts/drafts/ERC20Migrator.sol

@@ -44,7 +44,7 @@ contract ERC20Migrator {
      * @param legacyToken address of the old token contract
      */
     constructor (IERC20 legacyToken) public {
-        require(address(legacyToken) != address(0));
+        require(address(legacyToken) != address(0), "ERC20Migrator: legacy token is the zero address");
         _legacyToken = legacyToken;
     }
 
@@ -68,9 +68,10 @@ contract ERC20Migrator {
      * @param newToken_ the token that will be minted
      */
     function beginMigration(ERC20Mintable newToken_) public {
-        require(address(_newToken) == address(0));
-        require(address(newToken_) != address(0));
-        require(newToken_.isMinter(address(this)));
+        require(address(_newToken) == address(0), "ERC20Migrator: migration already started");
+        require(address(newToken_) != address(0), "ERC20Migrator: new token is the zero address");
+        //solhint-disable-next-line max-line-length
+        require(newToken_.isMinter(address(this)), "ERC20Migrator: not a minter for new token");
 
         _newToken = newToken_;
     }
@@ -82,7 +83,7 @@ contract ERC20Migrator {
      * @param amount amount of tokens to be migrated
      */
     function migrate(address account, uint256 amount) public {
-        require(address(_newToken) != address(0));
+        require(address(_newToken) != address(0), "ERC20Migrator: migration not started");
         _legacyToken.safeTransferFrom(account, address(this), amount);
         _newToken.mint(account, amount);
     }

+ 3 - 2
contracts/drafts/ERC20Snapshot.sol

@@ -101,8 +101,9 @@ contract ERC20Snapshot is ERC20 {
     function _valueAt(uint256 snapshotId, Snapshots storage snapshots)
         private view returns (bool, uint256)
     {
-        require(snapshotId > 0);
-        require(snapshotId <= _currentSnapshotId.current());
+        require(snapshotId > 0, "ERC20Snapshot: id is 0");
+        // solhint-disable-next-line max-line-length
+        require(snapshotId <= _currentSnapshotId.current(), "ERC20Snapshot: nonexistent id");
 
         uint256 index = snapshots.ids.findUpperBound(snapshotId);
 

+ 6 - 4
contracts/drafts/SignatureBouncer.sol

@@ -51,7 +51,7 @@ contract SignatureBouncer is SignerRole {
      * @dev Requires that a valid signature of a signer was provided.
      */
     modifier onlyValidSignature(bytes memory signature) {
-        require(_isValidSignature(msg.sender, signature));
+        require(_isValidSignature(msg.sender, signature), "SignatureBouncer: invalid signature for caller");
         _;
     }
 
@@ -59,7 +59,8 @@ contract SignatureBouncer is SignerRole {
      * @dev Requires that a valid signature with a specified method of a signer was provided.
      */
     modifier onlyValidSignatureAndMethod(bytes memory signature) {
-        require(_isValidSignatureAndMethod(msg.sender, signature));
+        // solhint-disable-next-line max-line-length
+        require(_isValidSignatureAndMethod(msg.sender, signature), "SignatureBouncer: invalid signature for caller and method");
         _;
     }
 
@@ -67,7 +68,8 @@ contract SignatureBouncer is SignerRole {
      * @dev Requires that a valid signature with a specified method and params of a signer was provided.
      */
     modifier onlyValidSignatureAndData(bytes memory signature) {
-        require(_isValidSignatureAndData(msg.sender, signature));
+        // solhint-disable-next-line max-line-length
+        require(_isValidSignatureAndData(msg.sender, signature), "SignatureBouncer: invalid signature for caller and data");
         _;
     }
 
@@ -97,7 +99,7 @@ contract SignatureBouncer is SignerRole {
      * @return bool
      */
     function _isValidSignatureAndData(address account, bytes memory signature) internal view returns (bool) {
-        require(msg.data.length > _SIGNATURE_SIZE);
+        require(msg.data.length > _SIGNATURE_SIZE, "SignatureBouncer: data is too short");
 
         bytes memory data = new bytes(msg.data.length - _SIGNATURE_SIZE);
         for (uint i = 0; i < data.length; i++) {

+ 6 - 6
contracts/drafts/SignedSafeMath.sol

@@ -18,10 +18,10 @@ library SignedSafeMath {
             return 0;
         }
 
-        require(!(a == -1 && b == INT256_MIN)); // This is the only case of overflow not detected by the check below
+        require(!(a == -1 && b == INT256_MIN), "SignedSafeMath: multiplication overflow");
 
         int256 c = a * b;
-        require(c / a == b);
+        require(c / a == b, "SignedSafeMath: multiplication overflow");
 
         return c;
     }
@@ -30,8 +30,8 @@ library SignedSafeMath {
      * @dev Integer division of two signed integers truncating the quotient, reverts on division by zero.
      */
     function div(int256 a, int256 b) internal pure returns (int256) {
-        require(b != 0); // Solidity only automatically asserts when dividing by 0
-        require(!(b == -1 && a == INT256_MIN)); // This is the only case of overflow
+        require(b != 0, "SignedSafeMath: division by zero");
+        require(!(b == -1 && a == INT256_MIN), "SignedSafeMath: division overflow");
 
         int256 c = a / b;
 
@@ -43,7 +43,7 @@ library SignedSafeMath {
      */
     function sub(int256 a, int256 b) internal pure returns (int256) {
         int256 c = a - b;
-        require((b >= 0 && c <= a) || (b < 0 && c > a));
+        require((b >= 0 && c <= a) || (b < 0 && c > a), "SignedSafeMath: subtraction overflow");
 
         return c;
     }
@@ -53,7 +53,7 @@ library SignedSafeMath {
      */
     function add(int256 a, int256 b) internal pure returns (int256) {
         int256 c = a + b;
-        require((b >= 0 && c >= a) || (b < 0 && c < a));
+        require((b >= 0 && c >= a) || (b < 0 && c < a), "SignedSafeMath: addition overflow");
 
         return c;
     }

+ 9 - 7
contracts/drafts/TokenVesting.sol

@@ -47,10 +47,12 @@ contract TokenVesting is Ownable {
      * @param revocable whether the vesting is revocable or not
      */
     constructor (address beneficiary, uint256 start, uint256 cliffDuration, uint256 duration, bool revocable) public {
-        require(beneficiary != address(0));
-        require(cliffDuration <= duration);
-        require(duration > 0);
-        require(start.add(duration) > block.timestamp);
+        require(beneficiary != address(0), "TokenVesting: beneficiary is the zero address");
+        // solhint-disable-next-line max-line-length
+        require(cliffDuration <= duration, "TokenVesting: cliff is longer than duration");
+        require(duration > 0, "TokenVesting: duration is 0");
+        // solhint-disable-next-line max-line-length
+        require(start.add(duration) > block.timestamp, "TokenVesting: final time is before current time");
 
         _beneficiary = beneficiary;
         _revocable = revocable;
@@ -115,7 +117,7 @@ contract TokenVesting is Ownable {
     function release(IERC20 token) public {
         uint256 unreleased = _releasableAmount(token);
 
-        require(unreleased > 0);
+        require(unreleased > 0, "TokenVesting: no tokens are due");
 
         _released[address(token)] = _released[address(token)].add(unreleased);
 
@@ -130,8 +132,8 @@ contract TokenVesting is Ownable {
      * @param token ERC20 token which is being vested
      */
     function revoke(IERC20 token) public onlyOwner {
-        require(_revocable);
-        require(!_revoked[address(token)]);
+        require(_revocable, "TokenVesting: cannot revoke");
+        require(!_revoked[address(token)], "TokenVesting: token already revoked");
 
         uint256 balance = token.balanceOf(address(this));
 

+ 1 - 1
contracts/examples/SampleCrowdsale.sol

@@ -48,6 +48,6 @@ contract SampleCrowdsale is CappedCrowdsale, RefundableCrowdsale, MintedCrowdsal
     {
         //As goal needs to be met for a successful crowdsale
         //the value needs to less or equal than a cap which is limit for accepted funds
-        require(goal <= cap);
+        require(goal <= cap, "SampleCrowdSale: goal is greater than cap");
     }
 }

+ 1 - 1
contracts/introspection/ERC165.sol

@@ -37,7 +37,7 @@ contract ERC165 is IERC165 {
      * @dev Internal method for registering an interface.
      */
     function _registerInterface(bytes4 interfaceId) internal {
-        require(interfaceId != 0xffffffff);
+        require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
         _supportedInterfaces[interfaceId] = true;
     }
 }

+ 2 - 2
contracts/lifecycle/Pausable.sol

@@ -27,7 +27,7 @@ contract Pausable is PauserRole {
      * @dev Modifier to make a function callable only when the contract is not paused.
      */
     modifier whenNotPaused() {
-        require(!_paused);
+        require(!_paused, "Pausable: paused");
         _;
     }
 
@@ -35,7 +35,7 @@ contract Pausable is PauserRole {
      * @dev Modifier to make a function callable only when the contract is paused.
      */
     modifier whenPaused() {
-        require(_paused);
+        require(_paused, "Pausable: not paused");
         _;
     }
 

+ 5 - 5
contracts/math/SafeMath.sol

@@ -17,7 +17,7 @@ library SafeMath {
         }
 
         uint256 c = a * b;
-        require(c / a == b);
+        require(c / a == b, "SafeMath: multiplication overflow");
 
         return c;
     }
@@ -27,7 +27,7 @@ library SafeMath {
      */
     function div(uint256 a, uint256 b) internal pure returns (uint256) {
         // Solidity only automatically asserts when dividing by 0
-        require(b > 0);
+        require(b > 0, "SafeMath: division by zero");
         uint256 c = a / b;
         // assert(a == b * c + a % b); // There is no case in which this doesn't hold
 
@@ -38,7 +38,7 @@ library SafeMath {
      * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
      */
     function sub(uint256 a, uint256 b) internal pure returns (uint256) {
-        require(b <= a);
+        require(b <= a, "SafeMath: subtraction overflow");
         uint256 c = a - b;
 
         return c;
@@ -49,7 +49,7 @@ library SafeMath {
      */
     function add(uint256 a, uint256 b) internal pure returns (uint256) {
         uint256 c = a + b;
-        require(c >= a);
+        require(c >= a, "SafeMath: addition overflow");
 
         return c;
     }
@@ -59,7 +59,7 @@ library SafeMath {
      * reverts when dividing by zero.
      */
     function mod(uint256 a, uint256 b) internal pure returns (uint256) {
-        require(b != 0);
+        require(b != 0, "SafeMath: modulo by zero");
         return a % b;
     }
 }

+ 1 - 1
contracts/mocks/ERC165/ERC165InterfacesSupported.sol

@@ -42,7 +42,7 @@ contract SupportsInterfaceWithLookupMock is IERC165 {
      * @dev Private method for registering an interface.
      */
     function _registerInterface(bytes4 interfaceId) internal {
-        require(interfaceId != 0xffffffff);
+        require(interfaceId != 0xffffffff, "ERC165InterfacesSupported: invalid interface id");
         _supportedInterfaces[interfaceId] = true;
     }
 }

+ 1 - 1
contracts/mocks/ERC721ReceiverMock.sol

@@ -16,7 +16,7 @@ contract ERC721ReceiverMock is IERC721Receiver {
     function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data)
         public returns (bytes4)
     {
-        require(!_reverts);
+        require(!_reverts, "ERC721ReceiverMock: reverting");
         emit Received(operator, from, tokenId, data, gasleft());
         return _retval;
     }

+ 1 - 1
contracts/mocks/ReentrancyAttack.sol

@@ -4,6 +4,6 @@ contract ReentrancyAttack {
     function callSender(bytes4 data) public {
         // solhint-disable-next-line avoid-low-level-calls
         (bool success,) = msg.sender.call(abi.encodeWithSelector(data));
-        require(success);
+        require(success, "ReentrancyAttack: failed call");
     }
 }

+ 1 - 1
contracts/mocks/ReentrancyMock.sol

@@ -26,7 +26,7 @@ contract ReentrancyMock is ReentrancyGuard {
             count();
             // solhint-disable-next-line avoid-low-level-calls
             (bool success,) = address(this).call(abi.encodeWithSignature("countThisRecursive(uint256)", n - 1));
-            require(success);
+            require(success, "ReentrancyMock: failed call");
         }
     }
 

+ 1 - 1
contracts/mocks/SafeERC20Helper.sol

@@ -26,7 +26,7 @@ contract ERC20ReturnFalseMock {
     }
 
     function allowance(address, address) public view returns (uint256) {
-        require(_dummy == 0);
+        require(_dummy == 0); // Duummy read from a state variable so that the function is view
         return 0;
     }
 }

+ 2 - 2
contracts/ownership/Ownable.sol

@@ -30,7 +30,7 @@ contract Ownable {
      * @dev Throws if called by any account other than the owner.
      */
     modifier onlyOwner() {
-        require(isOwner());
+        require(isOwner(), "Ownable: caller is not the owner");
         _;
     }
 
@@ -66,7 +66,7 @@ contract Ownable {
      * @param newOwner The address to transfer ownership to.
      */
     function _transferOwnership(address newOwner) internal {
-        require(newOwner != address(0));
+        require(newOwner != address(0), "Ownable: new owner is the zero address");
         emit OwnershipTransferred(_owner, newOwner);
         _owner = newOwner;
     }

+ 2 - 2
contracts/ownership/Secondary.sol

@@ -23,7 +23,7 @@ contract Secondary {
      * @dev Reverts if called from any account other than the primary.
      */
     modifier onlyPrimary() {
-        require(msg.sender == _primary);
+        require(msg.sender == _primary, "Secondary: caller is not the primary account");
         _;
     }
 
@@ -39,7 +39,7 @@ contract Secondary {
      * @param recipient The address of new primary.
      */
     function transferPrimary(address recipient) public onlyPrimary {
-        require(recipient != address(0));
+        require(recipient != address(0), "Secondary: new primary is the zero address");
         _primary = recipient;
         emit PrimaryTransferred(_primary);
     }

+ 8 - 7
contracts/payment/PaymentSplitter.sol

@@ -37,8 +37,9 @@ contract PaymentSplitter {
      * duplicates in `payees`.
      */
     constructor (address[] memory payees, uint256[] memory shares) public payable {
-        require(payees.length == shares.length);
-        require(payees.length > 0);
+        // solhint-disable-next-line max-line-length
+        require(payees.length == shares.length, "PaymentSplitter: payees and shares length mismatch");
+        require(payees.length > 0, "PaymentSplitter: no payees");
 
         for (uint256 i = 0; i < payees.length; i++) {
             _addPayee(payees[i], shares[i]);
@@ -98,12 +99,12 @@ contract PaymentSplitter {
      * total shares and their previous withdrawals.
      */
     function release(address payable account) public {
-        require(_shares[account] > 0);
+        require(_shares[account] > 0, "PaymentSplitter: account has no shares");
 
         uint256 totalReceived = address(this).balance.add(_totalReleased);
         uint256 payment = totalReceived.mul(_shares[account]).div(_totalShares).sub(_released[account]);
 
-        require(payment != 0);
+        require(payment != 0, "PaymentSplitter: account is not due payment");
 
         _released[account] = _released[account].add(payment);
         _totalReleased = _totalReleased.add(payment);
@@ -118,9 +119,9 @@ contract PaymentSplitter {
      * @param shares_ The number of shares owned by the payee.
      */
     function _addPayee(address account, uint256 shares_) private {
-        require(account != address(0));
-        require(shares_ > 0);
-        require(_shares[account] == 0);
+        require(account != address(0), "PaymentSplitter: account is the zero address");
+        require(shares_ > 0, "PaymentSplitter: shares are 0");
+        require(_shares[account] == 0, "PaymentSplitter: account already has shares");
 
         _payees.push(account);
         _shares[account] = shares_;

+ 1 - 1
contracts/payment/escrow/ConditionalEscrow.sol

@@ -16,7 +16,7 @@ contract ConditionalEscrow is Escrow {
     function withdrawalAllowed(address payee) public view returns (bool);
 
     function withdraw(address payable payee) public {
-        require(withdrawalAllowed(payee));
+        require(withdrawalAllowed(payee), "ConditionalEscrow: payee is not allowed to withdraw");
         super.withdraw(payee);
     }
 }

+ 5 - 5
contracts/payment/escrow/RefundEscrow.sol

@@ -27,7 +27,7 @@ contract RefundEscrow is ConditionalEscrow {
      * @param beneficiary The beneficiary of the deposits.
      */
     constructor (address payable beneficiary) public {
-        require(beneficiary != address(0));
+        require(beneficiary != address(0), "RefundEscrow: beneficiary is the zero address");
         _beneficiary = beneficiary;
         _state = State.Active;
     }
@@ -51,7 +51,7 @@ contract RefundEscrow is ConditionalEscrow {
      * @param refundee The address funds will be sent to if a refund occurs.
      */
     function deposit(address refundee) public payable {
-        require(_state == State.Active);
+        require(_state == State.Active, "RefundEscrow: can only deposit while active");
         super.deposit(refundee);
     }
 
@@ -60,7 +60,7 @@ contract RefundEscrow is ConditionalEscrow {
      * further deposits.
      */
     function close() public onlyPrimary {
-        require(_state == State.Active);
+        require(_state == State.Active, "RefundEscrow: can only close while active");
         _state = State.Closed;
         emit RefundsClosed();
     }
@@ -69,7 +69,7 @@ contract RefundEscrow is ConditionalEscrow {
      * @dev Allows for refunds to take place, rejecting further deposits.
      */
     function enableRefunds() public onlyPrimary {
-        require(_state == State.Active);
+        require(_state == State.Active, "RefundEscrow: can only enable refunds while active");
         _state = State.Refunding;
         emit RefundsEnabled();
     }
@@ -78,7 +78,7 @@ contract RefundEscrow is ConditionalEscrow {
      * @dev Withdraws the beneficiary's funds.
      */
     function beneficiaryWithdraw() public {
-        require(_state == State.Closed);
+        require(_state == State.Closed, "RefundEscrow: beneficiary can only withdraw while closed");
         _beneficiary.transfer(address(this).balance);
     }
 

+ 5 - 5
contracts/token/ERC20/ERC20.sol

@@ -125,7 +125,7 @@ contract ERC20 is IERC20 {
      * @param value The amount to be transferred.
      */
     function _transfer(address from, address to, uint256 value) internal {
-        require(to != address(0));
+        require(to != address(0), "ERC20: transfer to the zero address");
 
         _balances[from] = _balances[from].sub(value);
         _balances[to] = _balances[to].add(value);
@@ -140,7 +140,7 @@ contract ERC20 is IERC20 {
      * @param value The amount that will be created.
      */
     function _mint(address account, uint256 value) internal {
-        require(account != address(0));
+        require(account != address(0), "ERC20: mint to the zero address");
 
         _totalSupply = _totalSupply.add(value);
         _balances[account] = _balances[account].add(value);
@@ -154,7 +154,7 @@ contract ERC20 is IERC20 {
      * @param value The amount that will be burnt.
      */
     function _burn(address account, uint256 value) internal {
-        require(account != address(0));
+        require(account != address(0), "ERC20: burn from the zero address");
 
         _totalSupply = _totalSupply.sub(value);
         _balances[account] = _balances[account].sub(value);
@@ -168,8 +168,8 @@ contract ERC20 is IERC20 {
      * @param value The number of tokens that can be spent.
      */
     function _approve(address owner, address spender, uint256 value) internal {
-        require(spender != address(0));
-        require(owner != address(0));
+        require(owner != address(0), "ERC20: approve from the zero address");
+        require(spender != address(0), "ERC20: approve to the zero address");
 
         _allowed[owner][spender] = value;
         emit Approval(owner, spender, value);

+ 2 - 2
contracts/token/ERC20/ERC20Capped.sol

@@ -10,7 +10,7 @@ contract ERC20Capped is ERC20Mintable {
     uint256 private _cap;
 
     constructor (uint256 cap) public {
-        require(cap > 0);
+        require(cap > 0, "ERC20Capped: cap is 0");
         _cap = cap;
     }
 
@@ -22,7 +22,7 @@ contract ERC20Capped is ERC20Mintable {
     }
 
     function _mint(address account, uint256 value) internal {
-        require(totalSupply().add(value) <= _cap);
+        require(totalSupply().add(value) <= _cap, "ERC20Capped: cap exceeded");
         super._mint(account, value);
     }
 }

+ 9 - 5
contracts/token/ERC20/SafeERC20.sol

@@ -29,7 +29,10 @@ library SafeERC20 {
         // safeApprove should only be called when setting an initial allowance,
         // or when resetting it to zero. To increase and decrease it, use
         // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
-        require((value == 0) || (token.allowance(address(this), spender) == 0));
+        // solhint-disable-next-line max-line-length
+        require((value == 0) || (token.allowance(address(this), spender) == 0),
+            "SafeERC20: approve from non-zero to non-zero allowance"
+        );
         callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
     }
 
@@ -57,15 +60,16 @@ library SafeERC20 {
         //  1. The target address is checked to verify it contains contract code
         //  2. The call itself is made, and success asserted
         //  3. The return value is decoded, which in turn checks the size of the returned data.
-
-        require(address(token).isContract());
+        // solhint-disable-next-line max-line-length
+        require(address(token).isContract(), "SafeERC20: call to non-contract");
 
         // solhint-disable-next-line avoid-low-level-calls
         (bool success, bytes memory returndata) = address(token).call(data);
-        require(success);
+        require(success, "SafeERC20: low-level call failed");
 
         if (returndata.length > 0) { // Return data is optional
-            require(abi.decode(returndata, (bool)));
+            // solhint-disable-next-line max-line-length
+            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
         }
     }
 }

+ 3 - 3
contracts/token/ERC20/TokenTimelock.sol

@@ -21,7 +21,7 @@ contract TokenTimelock {
 
     constructor (IERC20 token, address beneficiary, uint256 releaseTime) public {
         // solhint-disable-next-line not-rely-on-time
-        require(releaseTime > block.timestamp);
+        require(releaseTime > block.timestamp, "TokenTimelock: release time is before current time");
         _token = token;
         _beneficiary = beneficiary;
         _releaseTime = releaseTime;
@@ -53,10 +53,10 @@ contract TokenTimelock {
      */
     function release() public {
         // solhint-disable-next-line not-rely-on-time
-        require(block.timestamp >= _releaseTime);
+        require(block.timestamp >= _releaseTime, "TokenTimelock: current time is before release time");
 
         uint256 amount = _token.balanceOf(address(this));
-        require(amount > 0);
+        require(amount > 0, "TokenTimelock: no tokens to release");
 
         _token.safeTransfer(_beneficiary, amount);
     }

+ 22 - 13
contracts/token/ERC721/ERC721.sol

@@ -59,7 +59,8 @@ contract ERC721 is ERC165, IERC721 {
      * @return uint256 representing the amount owned by the passed address
      */
     function balanceOf(address owner) public view returns (uint256) {
-        require(owner != address(0));
+        require(owner != address(0), "ERC721: balance query for the zero address");
+
         return _ownedTokensCount[owner].current();
     }
 
@@ -70,7 +71,8 @@ contract ERC721 is ERC165, IERC721 {
      */
     function ownerOf(uint256 tokenId) public view returns (address) {
         address owner = _tokenOwner[tokenId];
-        require(owner != address(0));
+        require(owner != address(0), "ERC721: owner query for nonexistent token");
+
         return owner;
     }
 
@@ -84,8 +86,11 @@ contract ERC721 is ERC165, IERC721 {
      */
     function approve(address to, uint256 tokenId) public {
         address owner = ownerOf(tokenId);
-        require(to != owner);
-        require(msg.sender == owner || isApprovedForAll(owner, msg.sender));
+        require(to != owner, "ERC721: transfer to current owner");
+
+        require(msg.sender == owner || isApprovedForAll(owner, msg.sender),
+            "ERC721: approve caller is not owner nor approved for all"
+        );
 
         _tokenApprovals[tokenId] = to;
         emit Approval(owner, to, tokenId);
@@ -98,7 +103,8 @@ contract ERC721 is ERC165, IERC721 {
      * @return address currently approved for the given token ID
      */
     function getApproved(uint256 tokenId) public view returns (address) {
-        require(_exists(tokenId));
+        require(_exists(tokenId), "ERC721: approved query for nonexistent token");
+
         return _tokenApprovals[tokenId];
     }
 
@@ -109,7 +115,8 @@ contract ERC721 is ERC165, IERC721 {
      * @param approved representing the status of the approval to be set
      */
     function setApprovalForAll(address to, bool approved) public {
-        require(to != msg.sender);
+        require(to != msg.sender, "ERC721: approve to caller");
+
         _operatorApprovals[msg.sender][to] = approved;
         emit ApprovalForAll(msg.sender, to, approved);
     }
@@ -133,7 +140,8 @@ contract ERC721 is ERC165, IERC721 {
      * @param tokenId uint256 ID of the token to be transferred
      */
     function transferFrom(address from, address to, uint256 tokenId) public {
-        require(_isApprovedOrOwner(msg.sender, tokenId));
+        //solhint-disable-next-line max-line-length
+        require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
 
         _transferFrom(from, to, tokenId);
     }
@@ -167,7 +175,7 @@ contract ERC721 is ERC165, IERC721 {
      */
     function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
         transferFrom(from, to, tokenId);
-        require(_checkOnERC721Received(from, to, tokenId, _data));
+        require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
     }
 
     /**
@@ -188,6 +196,7 @@ contract ERC721 is ERC165, IERC721 {
      * is an operator of the owner, or is the owner of the token
      */
     function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
+        require(_exists(tokenId), "ERC721: operator query for nonexistent token");
         address owner = ownerOf(tokenId);
         return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
     }
@@ -199,8 +208,8 @@ contract ERC721 is ERC165, IERC721 {
      * @param tokenId uint256 ID of the token to be minted
      */
     function _mint(address to, uint256 tokenId) internal {
-        require(to != address(0));
-        require(!_exists(tokenId));
+        require(to != address(0), "ERC721: mint to the zero address");
+        require(!_exists(tokenId), "ERC721: token already minted");
 
         _tokenOwner[tokenId] = to;
         _ownedTokensCount[to].increment();
@@ -216,7 +225,7 @@ contract ERC721 is ERC165, IERC721 {
      * @param tokenId uint256 ID of the token being burned
      */
     function _burn(address owner, uint256 tokenId) internal {
-        require(ownerOf(tokenId) == owner);
+        require(ownerOf(tokenId) == owner, "ERC721: burn of token that is not own");
 
         _clearApproval(tokenId);
 
@@ -243,8 +252,8 @@ contract ERC721 is ERC165, IERC721 {
      * @param tokenId uint256 ID of the token to be transferred
      */
     function _transferFrom(address from, address to, uint256 tokenId) internal {
-        require(ownerOf(tokenId) == from);
-        require(to != address(0));
+        require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
+        require(to != address(0), "ERC721: transfer to the zero address");
 
         _clearApproval(tokenId);
 

+ 2 - 1
contracts/token/ERC721/ERC721Burnable.sol

@@ -12,7 +12,8 @@ contract ERC721Burnable is ERC721 {
      * @param tokenId uint256 id of the ERC721 token to be burned.
      */
     function burn(uint256 tokenId) public {
-        require(_isApprovedOrOwner(msg.sender, tokenId));
+        //solhint-disable-next-line max-line-length
+        require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721Burnable: caller is not owner nor approved");
         _burn(tokenId);
     }
 }

+ 2 - 2
contracts/token/ERC721/ERC721Enumerable.sol

@@ -45,7 +45,7 @@ contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable {
      * @return uint256 token ID at the given index of the tokens list owned by the requested address
      */
     function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
-        require(index < balanceOf(owner));
+        require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
         return _ownedTokens[owner][index];
     }
 
@@ -64,7 +64,7 @@ contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable {
      * @return uint256 token ID at the given index of the tokens list
      */
     function tokenByIndex(uint256 index) public view returns (uint256) {
-        require(index < totalSupply());
+        require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
         return _allTokens[index];
     }
 

+ 2 - 2
contracts/token/ERC721/ERC721Metadata.sol

@@ -56,7 +56,7 @@ contract ERC721Metadata is ERC165, ERC721, IERC721Metadata {
      * @param tokenId uint256 ID of the token to query
      */
     function tokenURI(uint256 tokenId) external view returns (string memory) {
-        require(_exists(tokenId));
+        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
         return _tokenURIs[tokenId];
     }
 
@@ -67,7 +67,7 @@ contract ERC721Metadata is ERC165, ERC721, IERC721Metadata {
      * @param uri string URI to assign
      */
     function _setTokenURI(uint256 tokenId, string memory uri) internal {
-        require(_exists(tokenId));
+        require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
         _tokenURIs[tokenId] = uri;
     }
 

+ 1 - 1
contracts/utils/ReentrancyGuard.sol

@@ -27,6 +27,6 @@ contract ReentrancyGuard {
         _guardCounter += 1;
         uint256 localCounter = _guardCounter;
         _;
-        require(localCounter == _guardCounter);
+        require(localCounter == _guardCounter, "ReentrancyGuard: reentrant call");
     }
 }

+ 5 - 5
test/access/Roles.test.js

@@ -9,7 +9,7 @@ contract('Roles', function ([_, authorized, otherAuthorized, other]) {
   });
 
   it('reverts when querying roles for the zero account', async function () {
-    await shouldFail.reverting(this.roles.has(ZERO_ADDRESS));
+    await shouldFail.reverting.withMessage(this.roles.has(ZERO_ADDRESS), 'Roles: account is the zero address');
   });
 
   context('initially', function () {
@@ -28,11 +28,11 @@ contract('Roles', function ([_, authorized, otherAuthorized, other]) {
 
       it('reverts when adding roles to an already assigned account', async function () {
         await this.roles.add(authorized);
-        await shouldFail.reverting(this.roles.add(authorized));
+        await shouldFail.reverting.withMessage(this.roles.add(authorized), 'Roles: account already has role');
       });
 
       it('reverts when adding roles to the zero account', async function () {
-        await shouldFail.reverting(this.roles.add(ZERO_ADDRESS));
+        await shouldFail.reverting.withMessage(this.roles.add(ZERO_ADDRESS), 'Roles: account is the zero address');
       });
     });
   });
@@ -51,11 +51,11 @@ contract('Roles', function ([_, authorized, otherAuthorized, other]) {
       });
 
       it('reverts when removing unassigned roles', async function () {
-        await shouldFail.reverting(this.roles.remove(other));
+        await shouldFail.reverting.withMessage(this.roles.remove(other), 'Roles: account does not have role');
       });
 
       it('reverts when removing roles from the zero account', async function () {
-        await shouldFail.reverting(this.roles.remove(ZERO_ADDRESS));
+        await shouldFail.reverting.withMessage(this.roles.remove(ZERO_ADDRESS), 'Roles: account is the zero address');
       });
     });
   });

+ 21 - 7
test/behaviors/access/roles/PublicRole.behavior.js

@@ -39,7 +39,9 @@ function shouldBehaveLikePublicRole (authorized, otherAuthorized, [other], rolen
     }
 
     it('reverts when querying roles for the null account', async function () {
-      await shouldFail.reverting(this.contract[`is${rolename}`](ZERO_ADDRESS));
+      await shouldFail.reverting.withMessage(this.contract[`is${rolename}`](ZERO_ADDRESS),
+        'Roles: account is the zero address'
+      );
     });
 
     describe('access control', function () {
@@ -55,7 +57,9 @@ function shouldBehaveLikePublicRole (authorized, otherAuthorized, [other], rolen
         const from = other;
 
         it('reverts', async function () {
-          await shouldFail.reverting(this.contract[`only${rolename}Mock`]({ from }));
+          await shouldFail.reverting.withMessage(this.contract[`only${rolename}Mock`]({ from }),
+            `${rolename}Role: caller does not have the ${rolename} role`
+          );
         });
       });
     });
@@ -75,11 +79,15 @@ function shouldBehaveLikePublicRole (authorized, otherAuthorized, [other], rolen
         });
 
         it('reverts when adding role to an already assigned account', async function () {
-          await shouldFail.reverting(this.contract[`add${rolename}`](authorized, { from }));
+          await shouldFail.reverting.withMessage(this.contract[`add${rolename}`](authorized, { from }),
+            'Roles: account already has role'
+          );
         });
 
         it('reverts when adding role to the null account', async function () {
-          await shouldFail.reverting(this.contract[`add${rolename}`](ZERO_ADDRESS, { from }));
+          await shouldFail.reverting.withMessage(this.contract[`add${rolename}`](ZERO_ADDRESS, { from }),
+            'Roles: account is the zero address'
+          );
         });
       });
     });
@@ -101,11 +109,15 @@ function shouldBehaveLikePublicRole (authorized, otherAuthorized, [other], rolen
         });
 
         it('reverts when removing from an unassigned account', async function () {
-          await shouldFail.reverting(this.contract[`remove${rolename}`](other, { from }));
+          await shouldFail.reverting.withMessage(this.contract[`remove${rolename}`](other, { from }),
+            'Roles: account does not have role'
+          );
         });
 
         it('reverts when removing role from the null account', async function () {
-          await shouldFail.reverting(this.contract[`remove${rolename}`](ZERO_ADDRESS, { from }));
+          await shouldFail.reverting.withMessage(this.contract[`remove${rolename}`](ZERO_ADDRESS, { from }),
+            'Roles: account is the zero address'
+          );
         });
       });
     });
@@ -122,7 +134,9 @@ function shouldBehaveLikePublicRole (authorized, otherAuthorized, [other], rolen
       });
 
       it('reverts when renouncing unassigned role', async function () {
-        await shouldFail.reverting(this.contract[`renounce${rolename}`]({ from: other }));
+        await shouldFail.reverting.withMessage(this.contract[`renounce${rolename}`]({ from: other }),
+          'Roles: account does not have role'
+        );
       });
     });
   });

+ 3 - 1
test/crowdsale/AllowanceCrowdsale.test.js

@@ -75,7 +75,9 @@ contract('AllowanceCrowdsale', function ([_, investor, wallet, purchaser, tokenW
   describe('when token wallet is the zero address', function () {
     it('creation reverts', async function () {
       this.token = await SimpleToken.new({ from: tokenWallet });
-      await shouldFail.reverting(AllowanceCrowdsaleImpl.new(rate, wallet, this.token.address, ZERO_ADDRESS));
+      await shouldFail.reverting.withMessage(AllowanceCrowdsaleImpl.new(rate, wallet, this.token.address, ZERO_ADDRESS),
+        'AllowanceCrowdsale: token wallet is the zero address'
+      );
     });
   });
 });

+ 5 - 3
test/crowdsale/CappedCrowdsale.test.js

@@ -14,7 +14,9 @@ contract('CappedCrowdsale', function ([_, wallet]) {
   });
 
   it('rejects a cap of zero', async function () {
-    await shouldFail.reverting(CappedCrowdsaleImpl.new(rate, wallet, this.token.address, 0));
+    await shouldFail.reverting.withMessage(CappedCrowdsaleImpl.new(rate, wallet, this.token.address, 0),
+      'CappedCrowdsale: cap is 0'
+    );
   });
 
   context('with crowdsale', function () {
@@ -31,11 +33,11 @@ contract('CappedCrowdsale', function ([_, wallet]) {
 
       it('should reject payments outside cap', async function () {
         await this.crowdsale.send(cap);
-        await shouldFail.reverting(this.crowdsale.send(1));
+        await shouldFail.reverting.withMessage(this.crowdsale.send(1), 'CappedCrowdsale: cap exceeded');
       });
 
       it('should reject payments that exceed cap', async function () {
-        await shouldFail.reverting(this.crowdsale.send(cap.addn(1)));
+        await shouldFail.reverting.withMessage(this.crowdsale.send(cap.addn(1)), 'CappedCrowdsale: cap exceeded');
       });
     });
 

+ 14 - 12
test/crowdsale/Crowdsale.test.js

@@ -11,8 +11,9 @@ contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
   const expectedTokenAmount = rate.mul(value);
 
   it('requires a non-null token', async function () {
-    await shouldFail.reverting(
-      Crowdsale.new(rate, wallet, ZERO_ADDRESS)
+    await shouldFail.reverting.withMessage(
+      Crowdsale.new(rate, wallet, ZERO_ADDRESS),
+      'Crowdsale: token is the zero address'
     );
   });
 
@@ -22,14 +23,14 @@ contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
     });
 
     it('requires a non-zero rate', async function () {
-      await shouldFail.reverting(
-        Crowdsale.new(0, wallet, this.token.address)
+      await shouldFail.reverting.withMessage(
+        Crowdsale.new(0, wallet, this.token.address), 'Crowdsale: rate is 0'
       );
     });
 
     it('requires a non-null wallet', async function () {
-      await shouldFail.reverting(
-        Crowdsale.new(rate, ZERO_ADDRESS, this.token.address)
+      await shouldFail.reverting.withMessage(
+        Crowdsale.new(rate, ZERO_ADDRESS, this.token.address), 'Crowdsale: wallet is the zero address'
       );
     });
 
@@ -46,8 +47,8 @@ contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
           });
 
           it('reverts on zero-valued payments', async function () {
-            await shouldFail.reverting(
-              this.crowdsale.send(0, { from: purchaser })
+            await shouldFail.reverting.withMessage(
+              this.crowdsale.send(0, { from: purchaser }), 'Crowdsale: weiAmount is 0'
             );
           });
         });
@@ -58,14 +59,15 @@ contract('Crowdsale', function ([_, investor, wallet, purchaser]) {
           });
 
           it('reverts on zero-valued payments', async function () {
-            await shouldFail.reverting(
-              this.crowdsale.buyTokens(investor, { value: 0, from: purchaser })
+            await shouldFail.reverting.withMessage(
+              this.crowdsale.buyTokens(investor, { value: 0, from: purchaser }), 'Crowdsale: weiAmount is 0'
             );
           });
 
           it('requires a non-null beneficiary', async function () {
-            await shouldFail.reverting(
-              this.crowdsale.buyTokens(ZERO_ADDRESS, { value: value, from: purchaser })
+            await shouldFail.reverting.withMessage(
+              this.crowdsale.buyTokens(ZERO_ADDRESS, { value: value, from: purchaser }),
+              'Crowdsale: beneficiary is the zero address'
             );
           });
         });

+ 6 - 2
test/crowdsale/FinalizableCrowdsale.test.js

@@ -23,7 +23,9 @@ contract('FinalizableCrowdsale', function ([_, wallet, other]) {
   });
 
   it('cannot be finalized before ending', async function () {
-    await shouldFail.reverting(this.crowdsale.finalize({ from: other }));
+    await shouldFail.reverting.withMessage(this.crowdsale.finalize({ from: other }),
+      'FinalizableCrowdsale: not closed'
+    );
   });
 
   it('can be finalized by anyone after ending', async function () {
@@ -34,7 +36,9 @@ contract('FinalizableCrowdsale', function ([_, wallet, other]) {
   it('cannot be finalized twice', async function () {
     await time.increaseTo(this.afterClosingTime);
     await this.crowdsale.finalize({ from: other });
-    await shouldFail.reverting(this.crowdsale.finalize({ from: other }));
+    await shouldFail.reverting.withMessage(this.crowdsale.finalize({ from: other }),
+      'FinalizableCrowdsale: already finalized'
+    );
   });
 
   it('logs finalized', async function () {

+ 9 - 7
test/crowdsale/IncreasingPriceCrowdsale.test.js

@@ -26,21 +26,21 @@ contract('IncreasingPriceCrowdsale', function ([_, investor, wallet, purchaser])
     });
 
     it('reverts with a final rate larger than the initial rate', async function () {
-      await shouldFail.reverting(IncreasingPriceCrowdsaleImpl.new(
+      await shouldFail.reverting.withMessage(IncreasingPriceCrowdsaleImpl.new(
         this.startTime, this.closingTime, wallet, this.token.address, initialRate, initialRate.addn(1)
-      ));
+      ), 'IncreasingPriceCrowdsale: initial rate is not greater than final rate');
     });
 
     it('reverts with a final rate equal to the initial rate', async function () {
-      await shouldFail.reverting(IncreasingPriceCrowdsaleImpl.new(
+      await shouldFail.reverting.withMessage(IncreasingPriceCrowdsaleImpl.new(
         this.startTime, this.closingTime, wallet, this.token.address, initialRate, initialRate
-      ));
+      ), 'IncreasingPriceCrowdsale: initial rate is not greater than final rate');
     });
 
     it('reverts with a final rate of zero', async function () {
-      await shouldFail.reverting(IncreasingPriceCrowdsaleImpl.new(
+      await shouldFail.reverting.withMessage(IncreasingPriceCrowdsaleImpl.new(
         this.startTime, this.closingTime, wallet, this.token.address, initialRate, 0
-      ));
+      ), 'IncreasingPriceCrowdsale: final rate is 0');
     });
 
     context('with crowdsale', function () {
@@ -57,7 +57,9 @@ contract('IncreasingPriceCrowdsale', function ([_, investor, wallet, purchaser])
       });
 
       it('reverts when the base Crowdsale\'s rate function is called', async function () {
-        await shouldFail.reverting(this.crowdsale.rate());
+        await shouldFail.reverting.withMessage(this.crowdsale.rate(),
+          'IncreasingPriceCrowdsale: rate() called'
+        );
       });
 
       it('returns a rate of 0 before the crowdsale starts', async function () {

+ 18 - 6
test/crowdsale/IndividuallyCappedCrowdsale.test.js

@@ -34,7 +34,9 @@ contract('IndividuallyCappedCrowdsale', function (
     });
 
     it('reverts when a non-capper sets a cap', async function () {
-      await shouldFail.reverting(this.crowdsale.setCap(alice, capAlice, { from: other }));
+      await shouldFail.reverting.withMessage(this.crowdsale.setCap(alice, capAlice, { from: other }),
+        'CapperRole: caller does not have the Capper role'
+      );
     });
 
     context('with individual caps', function () {
@@ -52,21 +54,31 @@ contract('IndividuallyCappedCrowdsale', function (
 
         it('should reject payments outside cap', async function () {
           await this.crowdsale.buyTokens(alice, { value: capAlice });
-          await shouldFail.reverting(this.crowdsale.buyTokens(alice, { value: 1 }));
+          await shouldFail.reverting.withMessage(this.crowdsale.buyTokens(alice, { value: 1 }),
+            'IndividuallyCappedCrowdsale: beneficiary\'s cap exceeded'
+          );
         });
 
         it('should reject payments that exceed cap', async function () {
-          await shouldFail.reverting(this.crowdsale.buyTokens(alice, { value: capAlice.addn(1) }));
-          await shouldFail.reverting(this.crowdsale.buyTokens(bob, { value: capBob.addn(1) }));
+          await shouldFail.reverting.withMessage(this.crowdsale.buyTokens(alice, { value: capAlice.addn(1) }),
+            'IndividuallyCappedCrowdsale: beneficiary\'s cap exceeded'
+          );
+          await shouldFail.reverting.withMessage(this.crowdsale.buyTokens(bob, { value: capBob.addn(1) }),
+            'IndividuallyCappedCrowdsale: beneficiary\'s cap exceeded'
+          );
         });
 
         it('should manage independent caps', async function () {
           await this.crowdsale.buyTokens(alice, { value: lessThanCapAlice });
-          await shouldFail.reverting(this.crowdsale.buyTokens(bob, { value: lessThanCapAlice }));
+          await shouldFail.reverting.withMessage(this.crowdsale.buyTokens(bob, { value: lessThanCapAlice }),
+            'IndividuallyCappedCrowdsale: beneficiary\'s cap exceeded'
+          );
         });
 
         it('should default to a cap of zero', async function () {
-          await shouldFail.reverting(this.crowdsale.buyTokens(charlie, { value: lessThanCapBoth }));
+          await shouldFail.reverting.withMessage(this.crowdsale.buyTokens(charlie, { value: lessThanCapBoth }),
+            'IndividuallyCappedCrowdsale: beneficiary\'s cap exceeded'
+          );
         });
       });
 

+ 6 - 2
test/crowdsale/PausableCrowdsale.test.js

@@ -26,8 +26,12 @@ contract('PausableCrowdsale', function ([_, pauser, wallet, other]) {
     });
 
     it('purchases do not work', async function () {
-      await shouldFail.reverting(this.crowdsale.sendTransaction({ from: other, value }));
-      await shouldFail.reverting(this.crowdsale.buyTokens(other, { from: other, value }));
+      await shouldFail.reverting.withMessage(this.crowdsale.sendTransaction({ from: other, value }),
+        'Pausable: paused'
+      );
+      await shouldFail.reverting.withMessage(this.crowdsale.buyTokens(other, { from: other, value }),
+        'Pausable: paused'
+      );
     });
 
     context('after unpause', function () {

+ 6 - 2
test/crowdsale/PostDeliveryCrowdsale.test.js

@@ -41,7 +41,9 @@ contract('PostDeliveryCrowdsale', function ([_, investor, wallet, purchaser]) {
       });
 
       it('does not allow beneficiaries to withdraw tokens before crowdsale ends', async function () {
-        await shouldFail.reverting(this.crowdsale.withdrawTokens(investor));
+        await shouldFail.reverting.withMessage(this.crowdsale.withdrawTokens(investor),
+          'PostDeliveryCrowdsale: not closed'
+        );
       });
 
       context('after closing time', function () {
@@ -57,7 +59,9 @@ contract('PostDeliveryCrowdsale', function ([_, investor, wallet, purchaser]) {
 
         it('rejects multiple withdrawals', async function () {
           await this.crowdsale.withdrawTokens(investor);
-          await shouldFail.reverting(this.crowdsale.withdrawTokens(investor));
+          await shouldFail.reverting.withMessage(this.crowdsale.withdrawTokens(investor),
+            'PostDeliveryCrowdsale: beneficiary is not due any tokens'
+          );
         });
       });
     });

+ 12 - 5
test/crowdsale/RefundableCrowdsale.test.js

@@ -24,8 +24,9 @@ contract('RefundableCrowdsale', function ([_, wallet, investor, purchaser, other
   });
 
   it('rejects a goal of zero', async function () {
-    await shouldFail.reverting(
-      RefundableCrowdsaleImpl.new(this.openingTime, this.closingTime, rate, wallet, this.token.address, 0)
+    await shouldFail.reverting.withMessage(
+      RefundableCrowdsaleImpl.new(this.openingTime, this.closingTime, rate, wallet, this.token.address, 0),
+      'RefundableCrowdsale: goal is 0'
     );
   });
 
@@ -40,7 +41,9 @@ contract('RefundableCrowdsale', function ([_, wallet, investor, purchaser, other
 
     context('before opening time', function () {
       it('denies refunds', async function () {
-        await shouldFail.reverting(this.crowdsale.claimRefund(investor));
+        await shouldFail.reverting.withMessage(this.crowdsale.claimRefund(investor),
+          'RefundableCrowdsale: not finalized'
+        );
       });
     });
 
@@ -50,7 +53,9 @@ contract('RefundableCrowdsale', function ([_, wallet, investor, purchaser, other
       });
 
       it('denies refunds', async function () {
-        await shouldFail.reverting(this.crowdsale.claimRefund(investor));
+        await shouldFail.reverting.withMessage(this.crowdsale.claimRefund(investor),
+          'RefundableCrowdsale: not finalized'
+        );
       });
 
       context('with unreached goal', function () {
@@ -84,7 +89,9 @@ contract('RefundableCrowdsale', function ([_, wallet, investor, purchaser, other
           });
 
           it('denies refunds', async function () {
-            await shouldFail.reverting(this.crowdsale.claimRefund(investor));
+            await shouldFail.reverting.withMessage(this.crowdsale.claimRefund(investor),
+              'RefundableCrowdsale: goal reached'
+            );
           });
 
           it('forwards funds to wallet', async function () {

+ 12 - 4
test/crowdsale/RefundablePostDeliveryCrowdsale.test.js

@@ -42,7 +42,9 @@ contract('RefundablePostDeliveryCrowdsale', function ([_, investor, wallet, purc
       });
 
       it('does not allow beneficiaries to withdraw tokens before crowdsale ends', async function () {
-        await shouldFail.reverting(this.crowdsale.withdrawTokens(investor));
+        await shouldFail.reverting.withMessage(this.crowdsale.withdrawTokens(investor),
+          'RefundablePostDeliveryCrowdsale: not finalized'
+        );
       });
 
       context('after closing time and finalization', function () {
@@ -52,7 +54,9 @@ contract('RefundablePostDeliveryCrowdsale', function ([_, investor, wallet, purc
         });
 
         it('rejects token withdrawals', async function () {
-          await shouldFail.reverting(this.crowdsale.withdrawTokens(investor));
+          await shouldFail.reverting.withMessage(this.crowdsale.withdrawTokens(investor),
+            'RefundablePostDeliveryCrowdsale: goal not reached'
+          );
         });
       });
     });
@@ -70,7 +74,9 @@ contract('RefundablePostDeliveryCrowdsale', function ([_, investor, wallet, purc
       });
 
       it('does not allow beneficiaries to withdraw tokens before crowdsale ends', async function () {
-        await shouldFail.reverting(this.crowdsale.withdrawTokens(investor));
+        await shouldFail.reverting.withMessage(this.crowdsale.withdrawTokens(investor),
+          'RefundablePostDeliveryCrowdsale: not finalized'
+        );
       });
 
       context('after closing time and finalization', function () {
@@ -87,7 +93,9 @@ contract('RefundablePostDeliveryCrowdsale', function ([_, investor, wallet, purc
 
         it('rejects multiple withdrawals', async function () {
           await this.crowdsale.withdrawTokens(investor);
-          await shouldFail.reverting(this.crowdsale.withdrawTokens(investor));
+          await shouldFail.reverting.withMessage(this.crowdsale.withdrawTokens(investor),
+            'PostDeliveryCrowdsale: beneficiary is not due any tokens'
+          );
         });
       });
     });

+ 24 - 14
test/crowdsale/TimedCrowdsale.test.js

@@ -21,21 +21,21 @@ contract('TimedCrowdsale', function ([_, investor, wallet, purchaser]) {
   });
 
   it('reverts if the opening time is in the past', async function () {
-    await shouldFail.reverting(TimedCrowdsaleImpl.new(
+    await shouldFail.reverting.withMessage(TimedCrowdsaleImpl.new(
       (await time.latest()).sub(time.duration.days(1)), this.closingTime, rate, wallet, this.token.address
-    ));
+    ), 'TimedCrowdsale: opening time is before current time');
   });
 
   it('reverts if the closing time is before the opening time', async function () {
-    await shouldFail.reverting(TimedCrowdsaleImpl.new(
+    await shouldFail.reverting.withMessage(TimedCrowdsaleImpl.new(
       this.openingTime, this.openingTime.sub(time.duration.seconds(1)), rate, wallet, this.token.address
-    ));
+    ), 'TimedCrowdsale: opening time is not before closing time');
   });
 
   it('reverts if the closing time equals the opening time', async function () {
-    await shouldFail.reverting(TimedCrowdsaleImpl.new(
+    await shouldFail.reverting.withMessage(TimedCrowdsaleImpl.new(
       this.openingTime, this.openingTime, rate, wallet, this.token.address
-    ));
+    ), 'TimedCrowdsale: opening time is not before closing time');
   });
 
   context('with crowdsale', function () {
@@ -56,8 +56,10 @@ contract('TimedCrowdsale', function ([_, investor, wallet, purchaser]) {
     describe('accepting payments', function () {
       it('should reject payments before start', async function () {
         (await this.crowdsale.isOpen()).should.equal(false);
-        await shouldFail.reverting(this.crowdsale.send(value));
-        await shouldFail.reverting(this.crowdsale.buyTokens(investor, { from: purchaser, value: value }));
+        await shouldFail.reverting.withMessage(this.crowdsale.send(value), 'TimedCrowdsale: not open');
+        await shouldFail.reverting.withMessage(this.crowdsale.buyTokens(investor, { from: purchaser, value: value }),
+          'TimedCrowdsale: not open'
+        );
       });
 
       it('should accept payments after start', async function () {
@@ -69,25 +71,31 @@ contract('TimedCrowdsale', function ([_, investor, wallet, purchaser]) {
 
       it('should reject payments after end', async function () {
         await time.increaseTo(this.afterClosingTime);
-        await shouldFail.reverting(this.crowdsale.send(value));
-        await shouldFail.reverting(this.crowdsale.buyTokens(investor, { value: value, from: purchaser }));
+        await shouldFail.reverting.withMessage(this.crowdsale.send(value), 'TimedCrowdsale: not open');
+        await shouldFail.reverting.withMessage(this.crowdsale.buyTokens(investor, { value: value, from: purchaser }),
+          'TimedCrowdsale: not open'
+        );
       });
     });
 
     describe('extending closing time', function () {
       it('should not reduce duration', async function () {
         // Same date
-        await shouldFail.reverting(this.crowdsale.extendTime(this.closingTime));
+        await shouldFail.reverting.withMessage(this.crowdsale.extendTime(this.closingTime),
+          'TimedCrowdsale: new closing time is before current closing time'
+        );
 
         // Prescending date
         const newClosingTime = this.closingTime.sub(time.duration.seconds(1));
-        await shouldFail.reverting(this.crowdsale.extendTime(newClosingTime));
+        await shouldFail.reverting.withMessage(this.crowdsale.extendTime(newClosingTime),
+          'TimedCrowdsale: new closing time is before current closing time'
+        );
       });
 
       context('before crowdsale start', function () {
         beforeEach(async function () {
           (await this.crowdsale.isOpen()).should.equal(false);
-          await shouldFail.reverting(this.crowdsale.send(value));
+          await shouldFail.reverting.withMessage(this.crowdsale.send(value), 'TimedCrowdsale: not open');
         });
 
         it('it extends end time', async function () {
@@ -126,7 +134,9 @@ contract('TimedCrowdsale', function ([_, investor, wallet, purchaser]) {
 
         it('it reverts', async function () {
           const newClosingTime = await time.latest();
-          await shouldFail.reverting(this.crowdsale.extendTime(newClosingTime));
+          await shouldFail.reverting.withMessage(this.crowdsale.extendTime(newClosingTime),
+            'TimedCrowdsale: already closed'
+          );
         });
       });
     });

+ 6 - 2
test/crowdsale/WhitelistCrowdsale.test.js

@@ -20,8 +20,12 @@ contract('WhitelistCrowdsale', function ([_, wallet, whitelister, whitelisted, o
   }
 
   async function purchaseShouldFail (crowdsale, beneficiary, value) {
-    await shouldFail.reverting(crowdsale.buyTokens(beneficiary, { from: beneficiary, value }));
-    await shouldFail.reverting(crowdsale.sendTransaction({ from: beneficiary, value }));
+    await shouldFail.reverting.withMessage(crowdsale.buyTokens(beneficiary, { from: beneficiary, value }),
+      'WhitelistCrowdsale: beneficiary doesn\'t have the Whitelisted role'
+    );
+    await shouldFail.reverting.withMessage(crowdsale.sendTransaction({ from: beneficiary, value }),
+      'WhitelistCrowdsale: beneficiary doesn\'t have the Whitelisted role'
+    );
   }
 
   context('with no whitelisted addresses', function () {

+ 3 - 2
test/cryptography/ECDSA.test.js

@@ -118,8 +118,9 @@ contract('ECDSA', function ([_, other]) {
       it.skip('reverts', async function () {
         // Create the signature
         const signature = await web3.eth.sign(TEST_MESSAGE, other);
-        await shouldFail.reverting(
-          this.ecdsa.recover(TEST_MESSAGE.substring(2), signature)
+        await shouldFail.reverting.withMessage(
+          this.ecdsa.recover(TEST_MESSAGE.substring(2), signature),
+          'Failure message'
         );
       });
     });

+ 1 - 1
test/drafts/Counters.test.js

@@ -39,7 +39,7 @@ contract('Counters', function () {
 
     it('reverts if the current value is 0', async function () {
       await this.counter.decrement();
-      await shouldFail.reverting(this.counter.decrement());
+      await shouldFail.reverting.withMessage(this.counter.decrement(), 'SafeMath: subtraction overflow');
     });
 
     it('can be called multiple times', async function () {

+ 3 - 2
test/drafts/ERC1820Implementer.test.js

@@ -21,10 +21,11 @@ contract('ERC1820Implementer', function ([_, registryFunder, implementee, other]
     });
 
     it('reverts when attempting to set as implementer in the registry', async function () {
-      await shouldFail.reverting(
+      await shouldFail.reverting.withMessage(
         this.registry.setInterfaceImplementer(
           implementee, this.interfaceA, this.implementer.address, { from: implementee }
-        )
+        ),
+        'Does not implement the interface'
       );
     });
   });

+ 21 - 7
test/drafts/ERC20Migrator.test.js

@@ -9,7 +9,9 @@ contract('ERC20Migrator', function ([_, owner, recipient, anotherAccount]) {
   const totalSupply = new BN('200');
 
   it('reverts with a null legacy token address', async function () {
-    await shouldFail.reverting(ERC20Migrator.new(ZERO_ADDRESS));
+    await shouldFail.reverting.withMessage(ERC20Migrator.new(ZERO_ADDRESS),
+      'ERC20Migrator: legacy token is the zero address'
+    );
   });
 
   describe('with tokens and migrator', function () {
@@ -25,11 +27,15 @@ contract('ERC20Migrator', function ([_, owner, recipient, anotherAccount]) {
 
     describe('beginMigration', function () {
       it('reverts with a null new token address', async function () {
-        await shouldFail.reverting(this.migrator.beginMigration(ZERO_ADDRESS));
+        await shouldFail.reverting.withMessage(this.migrator.beginMigration(ZERO_ADDRESS),
+          'ERC20Migrator: new token is the zero address'
+        );
       });
 
       it('reverts if not a minter of the token', async function () {
-        await shouldFail.reverting(this.migrator.beginMigration(this.newToken.address));
+        await shouldFail.reverting.withMessage(this.migrator.beginMigration(this.newToken.address),
+          'ERC20Migrator: not a minter for new token'
+        );
       });
 
       it('succeeds if it is a minter of the token', async function () {
@@ -40,7 +46,9 @@ contract('ERC20Migrator', function ([_, owner, recipient, anotherAccount]) {
       it('reverts the second time it is called', async function () {
         await this.newToken.addMinter(this.migrator.address);
         await this.migrator.beginMigration(this.newToken.address);
-        await shouldFail.reverting(this.migrator.beginMigration(this.newToken.address));
+        await shouldFail.reverting.withMessage(this.migrator.beginMigration(this.newToken.address),
+          'ERC20Migrator: migration already started'
+        );
       });
     });
 
@@ -58,7 +66,9 @@ contract('ERC20Migrator', function ([_, owner, recipient, anotherAccount]) {
           });
 
           it('reverts', async function () {
-            await shouldFail.reverting(this.migrator.migrateAll(owner));
+            await shouldFail.reverting.withMessage(this.migrator.migrateAll(owner),
+              'ERC20Migrator: migration not started'
+            );
           });
         });
       });
@@ -72,7 +82,9 @@ contract('ERC20Migrator', function ([_, owner, recipient, anotherAccount]) {
           });
 
           it('reverts', async function () {
-            await shouldFail.reverting(this.migrator.migrate(owner, amount));
+            await shouldFail.reverting.withMessage(this.migrator.migrate(owner, amount),
+              'ERC20Migrator: migration not started'
+            );
           });
         });
       });
@@ -174,7 +186,9 @@ contract('ERC20Migrator', function ([_, owner, recipient, anotherAccount]) {
           const amount = baseAmount.addn(1);
 
           it('reverts', async function () {
-            await shouldFail.reverting(this.migrator.migrate(owner, amount));
+            await shouldFail.reverting.withMessage(this.migrator.migrate(owner, amount),
+              'SafeERC20: low-level call failed'
+            );
           });
         });
       });

+ 4 - 4
test/drafts/ERC20Snapshot.test.js

@@ -24,11 +24,11 @@ contract('ERC20Snapshot', function ([_, initialHolder, recipient, other]) {
 
   describe('totalSupplyAt', function () {
     it('reverts with a snapshot id of 0', async function () {
-      await shouldFail.reverting(this.token.totalSupplyAt(0));
+      await shouldFail.reverting.withMessage(this.token.totalSupplyAt(0), 'ERC20Snapshot: id is 0');
     });
 
     it('reverts with a not-yet-created snapshot id', async function () {
-      await shouldFail.reverting(this.token.totalSupplyAt(1));
+      await shouldFail.reverting.withMessage(this.token.totalSupplyAt(1), 'ERC20Snapshot: nonexistent id');
     });
 
     context('with initial snapshot', function () {
@@ -98,11 +98,11 @@ contract('ERC20Snapshot', function ([_, initialHolder, recipient, other]) {
 
   describe('balanceOfAt', function () {
     it('reverts with a snapshot id of 0', async function () {
-      await shouldFail.reverting(this.token.balanceOfAt(other, 0));
+      await shouldFail.reverting.withMessage(this.token.balanceOfAt(other, 0), 'ERC20Snapshot: id is 0');
     });
 
     it('reverts with a not-yet-created snapshot id', async function () {
-      await shouldFail.reverting(this.token.balanceOfAt(other, 1));
+      await shouldFail.reverting.withMessage(this.token.balanceOfAt(other, 1), 'ERC20Snapshot: nonexistent id');
     });
 
     context('with initial snapshot', function () {

+ 30 - 24
test/drafts/SignatureBouncer.test.js

@@ -30,21 +30,23 @@ contract('SignatureBouncer', function ([_, signer, otherSigner, other, authorize
       });
 
       it('does not allow invalid signature for sender', async function () {
-        await shouldFail.reverting(
-          this.sigBouncer.onlyWithValidSignature(INVALID_SIGNATURE, { from: authorizedUser })
+        await shouldFail.reverting.withMessage(
+          this.sigBouncer.onlyWithValidSignature(INVALID_SIGNATURE, { from: authorizedUser }),
+          'SignatureBouncer: invalid signature for caller'
         );
       });
 
       it('does not allow valid signature for other sender', async function () {
-        await shouldFail.reverting(
-          this.sigBouncer.onlyWithValidSignature(await this.signFor(authorizedUser), { from: other })
+        await shouldFail.reverting.withMessage(
+          this.sigBouncer.onlyWithValidSignature(await this.signFor(authorizedUser), { from: other }),
+          'SignatureBouncer: invalid signature for caller'
         );
       });
 
       it('does not allow valid signature for method for sender', async function () {
-        await shouldFail.reverting(
+        await shouldFail.reverting.withMessage(
           this.sigBouncer.onlyWithValidSignature(await this.signFor(authorizedUser, 'onlyWithValidSignature'),
-            { from: authorizedUser })
+            { from: authorizedUser }), 'SignatureBouncer: invalid signature for caller'
         );
       });
     });
@@ -57,29 +59,32 @@ contract('SignatureBouncer', function ([_, signer, otherSigner, other, authorize
       });
 
       it('does not allow invalid signature with correct method for sender', async function () {
-        await shouldFail.reverting(
-          this.sigBouncer.onlyWithValidSignatureAndMethod(INVALID_SIGNATURE, { from: authorizedUser })
+        await shouldFail.reverting.withMessage(
+          this.sigBouncer.onlyWithValidSignatureAndMethod(INVALID_SIGNATURE, { from: authorizedUser }),
+          'SignatureBouncer: invalid signature for caller and method'
         );
       });
 
       it('does not allow valid signature with correct method for other sender', async function () {
-        await shouldFail.reverting(
+        await shouldFail.reverting.withMessage(
           this.sigBouncer.onlyWithValidSignatureAndMethod(
             await this.signFor(authorizedUser, 'onlyWithValidSignatureAndMethod'), { from: other }
-          )
+          ),
+          'SignatureBouncer: invalid signature for caller and method'
         );
       });
 
       it('does not allow valid method signature with incorrect method for sender', async function () {
-        await shouldFail.reverting(
+        await shouldFail.reverting.withMessage(
           this.sigBouncer.onlyWithValidSignatureAndMethod(await this.signFor(authorizedUser, 'theWrongMethod'),
-            { from: authorizedUser })
+            { from: authorizedUser }), 'SignatureBouncer: invalid signature for caller and method'
         );
       });
 
       it('does not allow valid non-method signature method for sender', async function () {
-        await shouldFail.reverting(
-          this.sigBouncer.onlyWithValidSignatureAndMethod(await this.signFor(authorizedUser), { from: authorizedUser })
+        await shouldFail.reverting.withMessage(
+          this.sigBouncer.onlyWithValidSignatureAndMethod(await this.signFor(authorizedUser), { from: authorizedUser }),
+          'SignatureBouncer: invalid signature for caller and method'
         );
       });
     });
@@ -92,40 +97,41 @@ contract('SignatureBouncer', function ([_, signer, otherSigner, other, authorize
       });
 
       it('does not allow invalid signature with correct method and data for sender', async function () {
-        await shouldFail.reverting(
-          this.sigBouncer.onlyWithValidSignatureAndData(UINT_VALUE, INVALID_SIGNATURE, { from: authorizedUser })
+        await shouldFail.reverting.withMessage(
+          this.sigBouncer.onlyWithValidSignatureAndData(UINT_VALUE, INVALID_SIGNATURE, { from: authorizedUser }),
+          'SignatureBouncer: invalid signature for caller and data'
         );
       });
 
       it('does not allow valid signature with correct method and incorrect data for sender', async function () {
-        await shouldFail.reverting(
+        await shouldFail.reverting.withMessage(
           this.sigBouncer.onlyWithValidSignatureAndData(UINT_VALUE + 10,
             await this.signFor(authorizedUser, 'onlyWithValidSignatureAndData', [UINT_VALUE]),
             { from: authorizedUser }
-          )
+          ), 'SignatureBouncer: invalid signature for caller and data'
         );
       });
 
       it('does not allow valid signature with correct method and data for other sender', async function () {
-        await shouldFail.reverting(
+        await shouldFail.reverting.withMessage(
           this.sigBouncer.onlyWithValidSignatureAndData(UINT_VALUE,
             await this.signFor(authorizedUser, 'onlyWithValidSignatureAndData', [UINT_VALUE]),
             { from: other }
-          )
+          ), 'SignatureBouncer: invalid signature for caller and data'
         );
       });
 
       it('does not allow valid non-method signature for sender', async function () {
-        await shouldFail.reverting(
+        await shouldFail.reverting.withMessage(
           this.sigBouncer.onlyWithValidSignatureAndData(UINT_VALUE,
             await this.signFor(authorizedUser), { from: authorizedUser }
-          )
+          ), 'SignatureBouncer: invalid signature for caller and data'
         );
       });
 
       it('does not allow msg.data shorter than SIGNATURE_SIZE', async function () {
-        await shouldFail.reverting(
-          this.sigBouncer.tooShortMsgData({ from: authorizedUser })
+        await shouldFail.reverting.withMessage(
+          this.sigBouncer.tooShortMsgData({ from: authorizedUser }), 'SignatureBouncer: data is too short'
         );
       });
     });

+ 11 - 11
test/drafts/SignedSafeMath.test.js

@@ -13,9 +13,9 @@ contract('SignedSafeMath', function () {
     (await fn(rhs, lhs)).should.be.bignumber.equal(expected);
   }
 
-  async function testFailsCommutative (fn, lhs, rhs) {
-    await shouldFail.reverting(fn(lhs, rhs));
-    await shouldFail.reverting(fn(rhs, lhs));
+  async function testFailsCommutative (fn, lhs, rhs, reason) {
+    await shouldFail.reverting.withMessage(fn(lhs, rhs), reason);
+    await shouldFail.reverting.withMessage(fn(rhs, lhs), reason);
   }
 
   describe('add', function () {
@@ -37,14 +37,14 @@ contract('SignedSafeMath', function () {
       const a = MAX_INT256;
       const b = new BN('1');
 
-      await testFailsCommutative(this.safeMath.add, a, b);
+      await testFailsCommutative(this.safeMath.add, a, b, 'SignedSafeMath: addition overflow');
     });
 
     it('reverts on negative addition overflow', async function () {
       const a = MIN_INT256;
       const b = new BN('-1');
 
-      await testFailsCommutative(this.safeMath.add, a, b);
+      await testFailsCommutative(this.safeMath.add, a, b, 'SignedSafeMath: addition overflow');
     });
   });
 
@@ -69,14 +69,14 @@ contract('SignedSafeMath', function () {
       const a = MAX_INT256;
       const b = new BN('-1');
 
-      await shouldFail.reverting(this.safeMath.sub(a, b));
+      await shouldFail.reverting.withMessage(this.safeMath.sub(a, b), 'SignedSafeMath: subtraction overflow');
     });
 
     it('reverts on negative subtraction overflow', async function () {
       const a = MIN_INT256;
       const b = new BN('1');
 
-      await shouldFail.reverting(this.safeMath.sub(a, b));
+      await shouldFail.reverting.withMessage(this.safeMath.sub(a, b), 'SignedSafeMath: subtraction overflow');
     });
   });
 
@@ -99,14 +99,14 @@ contract('SignedSafeMath', function () {
       const a = MAX_INT256;
       const b = new BN('2');
 
-      await testFailsCommutative(this.safeMath.mul, a, b);
+      await testFailsCommutative(this.safeMath.mul, a, b, 'SignedSafeMath: multiplication overflow');
     });
 
     it('reverts when minimum integer is multiplied by -1', async function () {
       const a = MIN_INT256;
       const b = new BN('-1');
 
-      await testFailsCommutative(this.safeMath.mul, a, b);
+      await testFailsCommutative(this.safeMath.mul, a, b, 'SignedSafeMath: multiplication overflow');
     });
   });
 
@@ -137,14 +137,14 @@ contract('SignedSafeMath', function () {
       const a = new BN('-5678');
       const b = new BN('0');
 
-      await shouldFail.reverting(this.safeMath.div(a, b));
+      await shouldFail.reverting.withMessage(this.safeMath.div(a, b), 'SignedSafeMath: division by zero');
     });
 
     it('reverts on overflow, negative second', async function () {
       const a = new BN(MIN_INT256);
       const b = new BN('-1');
 
-      await shouldFail.reverting(this.safeMath.div(a, b));
+      await shouldFail.reverting.withMessage(this.safeMath.div(a, b), 'SignedSafeMath: division overflow');
     });
   });
 });

+ 20 - 11
test/drafts/TokenVesting.test.js

@@ -20,21 +20,23 @@ contract('TokenVesting', function ([_, owner, beneficiary]) {
 
     cliffDuration.should.be.bignumber.that.is.at.least(duration);
 
-    await shouldFail.reverting(
-      TokenVesting.new(beneficiary, this.start, cliffDuration, duration, true, { from: owner })
+    await shouldFail.reverting.withMessage(
+      TokenVesting.new(beneficiary, this.start, cliffDuration, duration, true, { from: owner }),
+      'TokenVesting: cliff is longer than duration'
     );
   });
 
   it('reverts with a null beneficiary', async function () {
-    await shouldFail.reverting(
-      TokenVesting.new(ZERO_ADDRESS, this.start, this.cliffDuration, this.duration, true, { from: owner })
+    await shouldFail.reverting.withMessage(
+      TokenVesting.new(ZERO_ADDRESS, this.start, this.cliffDuration, this.duration, true, { from: owner }),
+      'TokenVesting: beneficiary is the zero address'
     );
   });
 
   it('reverts with a null duration', async function () {
     // cliffDuration should also be 0, since the duration must be larger than the cliff
-    await shouldFail.reverting(
-      TokenVesting.new(beneficiary, this.start, 0, 0, true, { from: owner })
+    await shouldFail.reverting.withMessage(
+      TokenVesting.new(beneficiary, this.start, 0, 0, true, { from: owner }), 'TokenVesting: duration is 0'
     );
   });
 
@@ -42,8 +44,9 @@ contract('TokenVesting', function ([_, owner, beneficiary]) {
     const now = await time.latest();
 
     this.start = now.sub(this.duration).sub(time.duration.minutes(1));
-    await shouldFail.reverting(
-      TokenVesting.new(beneficiary, this.start, this.cliffDuration, this.duration, true, { from: owner })
+    await shouldFail.reverting.withMessage(
+      TokenVesting.new(beneficiary, this.start, this.cliffDuration, this.duration, true, { from: owner }),
+      'TokenVesting: final time is before current time'
     );
   });
 
@@ -65,7 +68,9 @@ contract('TokenVesting', function ([_, owner, beneficiary]) {
     });
 
     it('cannot be released before cliff', async function () {
-      await shouldFail.reverting(this.vesting.release(this.token.address));
+      await shouldFail.reverting.withMessage(this.vesting.release(this.token.address),
+        'TokenVesting: no tokens are due'
+      );
     });
 
     it('can be released after cliff', async function () {
@@ -121,7 +126,9 @@ contract('TokenVesting', function ([_, owner, beneficiary]) {
         beneficiary, this.start, this.cliffDuration, this.duration, false, { from: owner }
       );
 
-      await shouldFail.reverting(vesting.revoke(this.token.address, { from: owner }));
+      await shouldFail.reverting.withMessage(vesting.revoke(this.token.address, { from: owner }),
+        'TokenVesting: cannot revoke'
+      );
     });
 
     it('should return the non-vested tokens when revoked by owner', async function () {
@@ -148,7 +155,9 @@ contract('TokenVesting', function ([_, owner, beneficiary]) {
 
     it('should fail to be revoked a second time', async function () {
       await this.vesting.revoke(this.token.address, { from: owner });
-      await shouldFail.reverting(this.vesting.revoke(this.token.address, { from: owner }));
+      await shouldFail.reverting.withMessage(this.vesting.revoke(this.token.address, { from: owner }),
+        'TokenVesting: token already revoked'
+      );
     });
 
     function vestedAmount (total, now, start, cliffDuration, duration) {

+ 11 - 7
test/examples/SampleCrowdsale.test.js

@@ -38,8 +38,10 @@ contract('SampleCrowdsale', function ([_, deployer, owner, wallet, investor]) {
   });
 
   it('should not accept payments before start', async function () {
-    await shouldFail.reverting(this.crowdsale.send(ether('1')));
-    await shouldFail.reverting(this.crowdsale.buyTokens(investor, { from: investor, value: ether('1') }));
+    await shouldFail.reverting.withMessage(this.crowdsale.send(ether('1')), 'TimedCrowdsale: not open');
+    await shouldFail.reverting.withMessage(this.crowdsale.buyTokens(investor, { from: investor, value: ether('1') }),
+      'TimedCrowdsale: not open'
+    );
   });
 
   it('should accept payments during the sale', async function () {
@@ -55,14 +57,16 @@ contract('SampleCrowdsale', function ([_, deployer, owner, wallet, investor]) {
 
   it('should reject payments after end', async function () {
     await time.increaseTo(this.afterClosingTime);
-    await shouldFail.reverting(this.crowdsale.send(ether('1')));
-    await shouldFail.reverting(this.crowdsale.buyTokens(investor, { value: ether('1'), from: investor }));
+    await shouldFail.reverting.withMessage(this.crowdsale.send(ether('1')), 'TimedCrowdsale: not open');
+    await shouldFail.reverting.withMessage(this.crowdsale.buyTokens(investor, { value: ether('1'), from: investor }),
+      'TimedCrowdsale: not open'
+    );
   });
 
   it('should reject payments over cap', async function () {
     await time.increaseTo(this.openingTime);
     await this.crowdsale.send(CAP);
-    await shouldFail.reverting(this.crowdsale.send(1));
+    await shouldFail.reverting.withMessage(this.crowdsale.send(1), 'CappedCrowdsale: cap exceeded');
   });
 
   it('should allow finalization and transfer funds to wallet if the goal is reached', async function () {
@@ -93,9 +97,9 @@ contract('SampleCrowdsale', function ([_, deployer, owner, wallet, investor]) {
     const HIGH_GOAL = ether('30');
 
     it('creation reverts', async function () {
-      await shouldFail.reverting(SampleCrowdsale.new(
+      await shouldFail.reverting.withMessage(SampleCrowdsale.new(
         this.openingTime, this.closingTime, RATE, wallet, CAP, this.token.address, HIGH_GOAL
-      ));
+      ), 'SampleCrowdSale: goal is greater than cap');
     });
   });
 });

+ 1 - 1
test/introspection/ERC165.test.js

@@ -9,7 +9,7 @@ contract('ERC165', function () {
   });
 
   it('does not allow 0xffffffff', async function () {
-    await shouldFail.reverting(this.mock.registerInterface('0xffffffff'));
+    await shouldFail.reverting.withMessage(this.mock.registerInterface('0xffffffff'), 'ERC165: invalid interface id');
   });
 
   shouldSupportInterfaces([

+ 15 - 7
test/lifecycle/Pausable.test.js

@@ -30,7 +30,9 @@ contract('Pausable', function ([_, pauser, otherPauser, other, ...otherAccounts]
     });
 
     it('cannot take drastic measure in non-pause', async function () {
-      await shouldFail.reverting(this.pausable.drasticMeasure({ from: other }));
+      await shouldFail.reverting.withMessage(this.pausable.drasticMeasure({ from: other }),
+        'Pausable: not paused'
+      );
       (await this.pausable.drasticMeasureTaken()).should.equal(false);
     });
 
@@ -41,7 +43,9 @@ contract('Pausable', function ([_, pauser, otherPauser, other, ...otherAccounts]
       });
 
       it('reverts when pausing from non-pauser', async function () {
-        await shouldFail.reverting(this.pausable.pause({ from: other }));
+        await shouldFail.reverting.withMessage(this.pausable.pause({ from: other }),
+          'PauserRole: caller does not have the Pauser role'
+        );
       });
 
       context('when paused', function () {
@@ -54,7 +58,7 @@ contract('Pausable', function ([_, pauser, otherPauser, other, ...otherAccounts]
         });
 
         it('cannot perform normal process in pause', async function () {
-          await shouldFail.reverting(this.pausable.normalProcess({ from: other }));
+          await shouldFail.reverting.withMessage(this.pausable.normalProcess({ from: other }), 'Pausable: paused');
         });
 
         it('can take a drastic measure in a pause', async function () {
@@ -63,7 +67,7 @@ contract('Pausable', function ([_, pauser, otherPauser, other, ...otherAccounts]
         });
 
         it('reverts when re-pausing', async function () {
-          await shouldFail.reverting(this.pausable.pause({ from: pauser }));
+          await shouldFail.reverting.withMessage(this.pausable.pause({ from: pauser }), 'Pausable: paused');
         });
 
         describe('unpausing', function () {
@@ -73,7 +77,9 @@ contract('Pausable', function ([_, pauser, otherPauser, other, ...otherAccounts]
           });
 
           it('reverts when unpausing from non-pauser', async function () {
-            await shouldFail.reverting(this.pausable.unpause({ from: other }));
+            await shouldFail.reverting.withMessage(this.pausable.unpause({ from: other }),
+              'PauserRole: caller does not have the Pauser role'
+            );
           });
 
           context('when unpaused', function () {
@@ -92,11 +98,13 @@ contract('Pausable', function ([_, pauser, otherPauser, other, ...otherAccounts]
             });
 
             it('should prevent drastic measure', async function () {
-              await shouldFail.reverting(this.pausable.drasticMeasure({ from: other }));
+              await shouldFail.reverting.withMessage(this.pausable.drasticMeasure({ from: other }),
+                'Pausable: not paused'
+              );
             });
 
             it('reverts when re-unpausing', async function () {
-              await shouldFail.reverting(this.pausable.unpause({ from: pauser }));
+              await shouldFail.reverting.withMessage(this.pausable.unpause({ from: pauser }), 'Pausable: not paused');
             });
           });
         });

+ 8 - 8
test/math/SafeMath.test.js

@@ -13,9 +13,9 @@ contract('SafeMath', function () {
     (await fn(rhs, lhs)).should.be.bignumber.equal(expected);
   }
 
-  async function testFailsCommutative (fn, lhs, rhs) {
-    await shouldFail.reverting(fn(lhs, rhs));
-    await shouldFail.reverting(fn(rhs, lhs));
+  async function testFailsCommutative (fn, lhs, rhs, reason) {
+    await shouldFail.reverting.withMessage(fn(lhs, rhs), reason);
+    await shouldFail.reverting.withMessage(fn(rhs, lhs), reason);
   }
 
   describe('add', function () {
@@ -30,7 +30,7 @@ contract('SafeMath', function () {
       const a = MAX_UINT256;
       const b = new BN('1');
 
-      await testFailsCommutative(this.safeMath.add, a, b);
+      await testFailsCommutative(this.safeMath.add, a, b, 'SafeMath: addition overflow');
     });
   });
 
@@ -46,7 +46,7 @@ contract('SafeMath', function () {
       const a = new BN('1234');
       const b = new BN('5678');
 
-      await shouldFail.reverting(this.safeMath.sub(a, b));
+      await shouldFail.reverting.withMessage(this.safeMath.sub(a, b), 'SafeMath: subtraction overflow');
     });
   });
 
@@ -69,7 +69,7 @@ contract('SafeMath', function () {
       const a = MAX_UINT256;
       const b = new BN('2');
 
-      await testFailsCommutative(this.safeMath.mul, a, b);
+      await testFailsCommutative(this.safeMath.mul, a, b, 'SafeMath: multiplication overflow');
     });
   });
 
@@ -99,7 +99,7 @@ contract('SafeMath', function () {
       const a = new BN('5678');
       const b = new BN('0');
 
-      await shouldFail.reverting(this.safeMath.div(a, b));
+      await shouldFail.reverting.withMessage(this.safeMath.div(a, b), 'SafeMath: division by zero');
     });
   });
 
@@ -138,7 +138,7 @@ contract('SafeMath', function () {
       const a = new BN('5678');
       const b = new BN('0');
 
-      await shouldFail.reverting(this.safeMath.mod(a, b));
+      await shouldFail.reverting.withMessage(this.safeMath.mod(a, b), 'SafeMath: modulo by zero');
     });
   });
 });

+ 12 - 3
test/ownership/Ownable.behavior.js

@@ -17,11 +17,17 @@ function shouldBehaveLikeOwnable (owner, [other]) {
     });
 
     it('should prevent non-owners from transferring', async function () {
-      await shouldFail.reverting(this.ownable.transferOwnership(other, { from: other }));
+      await shouldFail.reverting.withMessage(
+        this.ownable.transferOwnership(other, { from: other }),
+        'Ownable: caller is not the owner'
+      );
     });
 
     it('should guard ownership against stuck state', async function () {
-      await shouldFail.reverting(this.ownable.transferOwnership(ZERO_ADDRESS, { from: owner }));
+      await shouldFail.reverting.withMessage(
+        this.ownable.transferOwnership(ZERO_ADDRESS, { from: owner }),
+        'Ownable: new owner is the zero address'
+      );
     });
 
     it('loses owner after renouncement', async function () {
@@ -32,7 +38,10 @@ function shouldBehaveLikeOwnable (owner, [other]) {
     });
 
     it('should prevent non-owners from renouncement', async function () {
-      await shouldFail.reverting(this.ownable.renounceOwnership({ from: other }));
+      await shouldFail.reverting.withMessage(
+        this.ownable.renounceOwnership({ from: other }),
+        'Ownable: caller is not the owner'
+      );
     });
   });
 }

+ 12 - 4
test/ownership/Secondary.test.js

@@ -18,7 +18,9 @@ contract('Secondary', function ([_, primary, newPrimary, other]) {
     });
 
     it('reverts when anyone calls onlyPrimary functions', async function () {
-      await shouldFail.reverting(this.secondary.onlyPrimaryMock({ from: other }));
+      await shouldFail.reverting.withMessage(this.secondary.onlyPrimaryMock({ from: other }),
+        'Secondary: caller is not the primary account'
+      );
     });
   });
 
@@ -30,11 +32,15 @@ contract('Secondary', function ([_, primary, newPrimary, other]) {
     });
 
     it('reverts when transferring to the null address', async function () {
-      await shouldFail.reverting(this.secondary.transferPrimary(ZERO_ADDRESS, { from: primary }));
+      await shouldFail.reverting.withMessage(this.secondary.transferPrimary(ZERO_ADDRESS, { from: primary }),
+        'Secondary: new primary is the zero address'
+      );
     });
 
     it('reverts when called by anyone', async function () {
-      await shouldFail.reverting(this.secondary.transferPrimary(newPrimary, { from: other }));
+      await shouldFail.reverting.withMessage(this.secondary.transferPrimary(newPrimary, { from: other }),
+        'Secondary: caller is not the primary account'
+      );
     });
 
     context('with new primary', function () {
@@ -47,7 +53,9 @@ contract('Secondary', function ([_, primary, newPrimary, other]) {
       });
 
       it('reverts when the old primary account calls onlyPrimary functions', async function () {
-        await shouldFail.reverting(this.secondary.onlyPrimaryMock({ from: primary }));
+        await shouldFail.reverting.withMessage(this.secondary.onlyPrimaryMock({ from: primary }),
+          'Secondary: caller is not the primary account'
+        );
       });
     });
   });

+ 22 - 8
test/payment/PaymentSplitter.test.js

@@ -7,27 +7,37 @@ contract('PaymentSplitter', function ([_, owner, payee1, payee2, payee3, nonpaye
   const amount = ether('1');
 
   it('rejects an empty set of payees', async function () {
-    await shouldFail.reverting(PaymentSplitter.new([], []));
+    await shouldFail.reverting.withMessage(PaymentSplitter.new([], []), 'PaymentSplitter: no payees');
   });
 
   it('rejects more payees than shares', async function () {
-    await shouldFail.reverting(PaymentSplitter.new([payee1, payee2, payee3], [20, 30]));
+    await shouldFail.reverting.withMessage(PaymentSplitter.new([payee1, payee2, payee3], [20, 30]),
+      'PaymentSplitter: payees and shares length mismatch'
+    );
   });
 
   it('rejects more shares than payees', async function () {
-    await shouldFail.reverting(PaymentSplitter.new([payee1, payee2], [20, 30, 40]));
+    await shouldFail.reverting.withMessage(PaymentSplitter.new([payee1, payee2], [20, 30, 40]),
+      'PaymentSplitter: payees and shares length mismatch'
+    );
   });
 
   it('rejects null payees', async function () {
-    await shouldFail.reverting(PaymentSplitter.new([payee1, ZERO_ADDRESS], [20, 30]));
+    await shouldFail.reverting.withMessage(PaymentSplitter.new([payee1, ZERO_ADDRESS], [20, 30]),
+      'PaymentSplitter: account is the zero address'
+    );
   });
 
   it('rejects zero-valued shares', async function () {
-    await shouldFail.reverting(PaymentSplitter.new([payee1, payee2], [20, 0]));
+    await shouldFail.reverting.withMessage(PaymentSplitter.new([payee1, payee2], [20, 0]),
+      'PaymentSplitter: shares are 0'
+    );
   });
 
   it('rejects repeated payees', async function () {
-    await shouldFail.reverting(PaymentSplitter.new([payee1, payee1], [20, 30]));
+    await shouldFail.reverting.withMessage(PaymentSplitter.new([payee1, payee1], [20, 30]),
+      'PaymentSplitter: account already has shares'
+    );
   });
 
   context('once deployed', function () {
@@ -64,12 +74,16 @@ contract('PaymentSplitter', function ([_, owner, payee1, payee2, payee3, nonpaye
     });
 
     it('should throw if no funds to claim', async function () {
-      await shouldFail.reverting(this.contract.release(payee1));
+      await shouldFail.reverting.withMessage(this.contract.release(payee1),
+        'PaymentSplitter: account is not due payment'
+      );
     });
 
     it('should throw if non-payee want to claim', async function () {
       await send.ether(payer1, this.contract.address, amount);
-      await shouldFail.reverting(this.contract.release(nonpayee1));
+      await shouldFail.reverting.withMessage(this.contract.release(nonpayee1),
+        'PaymentSplitter: account has no shares'
+      );
     });
 
     it('should distribute funds to payees', async function () {

+ 3 - 1
test/payment/escrow/ConditionalEscrow.test.js

@@ -26,7 +26,9 @@ contract('ConditionalEscrow', function ([_, owner, payee, ...otherAccounts]) {
     it('reverts on withdrawals', async function () {
       await this.escrow.deposit(payee, { from: owner, value: amount });
 
-      await shouldFail.reverting(this.escrow.withdraw(payee, { from: owner }));
+      await shouldFail.reverting.withMessage(this.escrow.withdraw(payee, { from: owner }),
+        'ConditionalEscrow: payee is not allowed to withdraw'
+      );
     });
   });
 });

+ 6 - 2
test/payment/escrow/Escrow.behavior.js

@@ -18,7 +18,9 @@ function shouldBehaveLikeEscrow (primary, [payee1, payee2]) {
       });
 
       it('only the primary account can deposit', async function () {
-        await shouldFail.reverting(this.escrow.deposit(payee1, { from: payee2 }));
+        await shouldFail.reverting.withMessage(this.escrow.deposit(payee1, { from: payee2 }),
+          'Secondary: caller is not the primary account'
+        );
       });
 
       it('emits a deposited event', async function () {
@@ -68,7 +70,9 @@ function shouldBehaveLikeEscrow (primary, [payee1, payee2]) {
       });
 
       it('only the primary account can withdraw', async function () {
-        await shouldFail.reverting(this.escrow.withdraw(payee1, { from: payee1 }));
+        await shouldFail.reverting.withMessage(this.escrow.withdraw(payee1, { from: payee1 }),
+          'Secondary: caller is not the primary account'
+        );
       });
 
       it('emits a withdrawn event', async function () {

+ 38 - 14
test/payment/escrow/RefundEscrow.test.js

@@ -8,8 +8,8 @@ contract('RefundEscrow', function ([_, primary, beneficiary, refundee1, refundee
   const refundees = [refundee1, refundee2];
 
   it('requires a non-null beneficiary', async function () {
-    await shouldFail.reverting(
-      RefundEscrow.new(ZERO_ADDRESS, { from: primary })
+    await shouldFail.reverting.withMessage(
+      RefundEscrow.new(ZERO_ADDRESS, { from: primary }), 'RefundEscrow: beneficiary is the zero address'
     );
   });
 
@@ -32,17 +32,23 @@ contract('RefundEscrow', function ([_, primary, beneficiary, refundee1, refundee
 
       it('does not refund refundees', async function () {
         await this.escrow.deposit(refundee1, { from: primary, value: amount });
-        await shouldFail.reverting(this.escrow.withdraw(refundee1));
+        await shouldFail.reverting.withMessage(this.escrow.withdraw(refundee1),
+          'ConditionalEscrow: payee is not allowed to withdraw'
+        );
       });
 
       it('does not allow beneficiary withdrawal', async function () {
         await this.escrow.deposit(refundee1, { from: primary, value: amount });
-        await shouldFail.reverting(this.escrow.beneficiaryWithdraw());
+        await shouldFail.reverting.withMessage(this.escrow.beneficiaryWithdraw(),
+          'RefundEscrow: beneficiary can only withdraw while closed'
+        );
       });
     });
 
     it('only the primary account can enter closed state', async function () {
-      await shouldFail.reverting(this.escrow.close({ from: beneficiary }));
+      await shouldFail.reverting.withMessage(this.escrow.close({ from: beneficiary }),
+        'Secondary: caller is not the primary account'
+      );
 
       const { logs } = await this.escrow.close({ from: primary });
       expectEvent.inLogs(logs, 'RefundsClosed');
@@ -56,11 +62,15 @@ contract('RefundEscrow', function ([_, primary, beneficiary, refundee1, refundee
       });
 
       it('rejects deposits', async function () {
-        await shouldFail.reverting(this.escrow.deposit(refundee1, { from: primary, value: amount }));
+        await shouldFail.reverting.withMessage(this.escrow.deposit(refundee1, { from: primary, value: amount }),
+          'RefundEscrow: can only deposit while active'
+        );
       });
 
       it('does not refund refundees', async function () {
-        await shouldFail.reverting(this.escrow.withdraw(refundee1));
+        await shouldFail.reverting.withMessage(this.escrow.withdraw(refundee1),
+          'ConditionalEscrow: payee is not allowed to withdraw'
+        );
       });
 
       it('allows beneficiary withdrawal', async function () {
@@ -70,16 +80,22 @@ contract('RefundEscrow', function ([_, primary, beneficiary, refundee1, refundee
       });
 
       it('prevents entering the refund state', async function () {
-        await shouldFail.reverting(this.escrow.enableRefunds({ from: primary }));
+        await shouldFail.reverting.withMessage(this.escrow.enableRefunds({ from: primary }),
+          'RefundEscrow: can only enable refunds while active'
+        );
       });
 
       it('prevents re-entering the closed state', async function () {
-        await shouldFail.reverting(this.escrow.close({ from: primary }));
+        await shouldFail.reverting.withMessage(this.escrow.close({ from: primary }),
+          'RefundEscrow: can only close while active'
+        );
       });
     });
 
     it('only the primary account can enter refund state', async function () {
-      await shouldFail.reverting(this.escrow.enableRefunds({ from: beneficiary }));
+      await shouldFail.reverting.withMessage(this.escrow.enableRefunds({ from: beneficiary }),
+        'Secondary: caller is not the primary account'
+      );
 
       const { logs } = await this.escrow.enableRefunds({ from: primary });
       expectEvent.inLogs(logs, 'RefundsEnabled');
@@ -93,7 +109,9 @@ contract('RefundEscrow', function ([_, primary, beneficiary, refundee1, refundee
       });
 
       it('rejects deposits', async function () {
-        await shouldFail.reverting(this.escrow.deposit(refundee1, { from: primary, value: amount }));
+        await shouldFail.reverting.withMessage(this.escrow.deposit(refundee1, { from: primary, value: amount }),
+          'RefundEscrow: can only deposit while active'
+        );
       });
 
       it('refunds refundees', async function () {
@@ -105,15 +123,21 @@ contract('RefundEscrow', function ([_, primary, beneficiary, refundee1, refundee
       });
 
       it('does not allow beneficiary withdrawal', async function () {
-        await shouldFail.reverting(this.escrow.beneficiaryWithdraw());
+        await shouldFail.reverting.withMessage(this.escrow.beneficiaryWithdraw(),
+          'RefundEscrow: beneficiary can only withdraw while closed'
+        );
       });
 
       it('prevents entering the closed state', async function () {
-        await shouldFail.reverting(this.escrow.close({ from: primary }));
+        await shouldFail.reverting.withMessage(this.escrow.close({ from: primary }),
+          'RefundEscrow: can only close while active'
+        );
       });
 
       it('prevents re-entering the refund state', async function () {
-        await shouldFail.reverting(this.escrow.enableRefunds({ from: primary }));
+        await shouldFail.reverting.withMessage(this.escrow.enableRefunds({ from: primary }),
+          'RefundEscrow: can only enable refunds while active'
+        );
       });
     });
   });

+ 56 - 23
test/token/ERC20/ERC20.test.js

@@ -37,7 +37,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
         const amount = initialSupply.addn(1);
 
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.transfer(to, amount, { from: initialHolder }));
+          await shouldFail.reverting.withMessage(this.token.transfer(to, amount, { from: initialHolder }),
+            'SafeMath: subtraction overflow'
+          );
         });
       });
 
@@ -68,7 +70,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
       const to = ZERO_ADDRESS;
 
       it('reverts', async function () {
-        await shouldFail.reverting(this.token.transfer(to, initialSupply, { from: initialHolder }));
+        await shouldFail.reverting.withMessage(this.token.transfer(to, initialSupply, { from: initialHolder }),
+          'ERC20: transfer to the zero address'
+        );
       });
     });
   });
@@ -126,7 +130,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
           const amount = initialSupply.addn(1);
 
           it('reverts', async function () {
-            await shouldFail.reverting(this.token.transferFrom(initialHolder, to, amount, { from: spender }));
+            await shouldFail.reverting.withMessage(this.token.transferFrom(
+              initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
+            );
           });
         });
       });
@@ -140,7 +146,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
           const amount = initialSupply;
 
           it('reverts', async function () {
-            await shouldFail.reverting(this.token.transferFrom(initialHolder, to, amount, { from: spender }));
+            await shouldFail.reverting.withMessage(this.token.transferFrom(
+              initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
+            );
           });
         });
 
@@ -148,7 +156,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
           const amount = initialSupply.addn(1);
 
           it('reverts', async function () {
-            await shouldFail.reverting(this.token.transferFrom(initialHolder, to, amount, { from: spender }));
+            await shouldFail.reverting.withMessage(this.token.transferFrom(
+              initialHolder, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
+            );
           });
         });
       });
@@ -163,7 +173,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
       });
 
       it('reverts', async function () {
-        await shouldFail.reverting(this.token.transferFrom(initialHolder, to, amount, { from: spender }));
+        await shouldFail.reverting.withMessage(this.token.transferFrom(
+          initialHolder, to, amount, { from: spender }), 'ERC20: transfer to the zero address'
+        );
       });
     });
   });
@@ -175,7 +187,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
       function shouldDecreaseApproval (amount) {
         describe('when there was no approved amount before', function () {
           it('reverts', async function () {
-            await shouldFail.reverting(this.token.decreaseAllowance(spender, amount, { from: initialHolder }));
+            await shouldFail.reverting.withMessage(this.token.decreaseAllowance(
+              spender, amount, { from: initialHolder }), 'SafeMath: subtraction overflow'
+            );
           });
         });
 
@@ -208,8 +222,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
           });
 
           it('reverts when more than the full allowance is removed', async function () {
-            await shouldFail.reverting(
-              this.token.decreaseAllowance(spender, approvedAmount.addn(1), { from: initialHolder })
+            await shouldFail.reverting.withMessage(
+              this.token.decreaseAllowance(spender, approvedAmount.addn(1), { from: initialHolder }),
+              'SafeMath: subtraction overflow'
             );
           });
         });
@@ -233,7 +248,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
       const spender = ZERO_ADDRESS;
 
       it('reverts', async function () {
-        await shouldFail.reverting(this.token.decreaseAllowance(spender, amount, { from: initialHolder }));
+        await shouldFail.reverting.withMessage(this.token.decreaseAllowance(
+          spender, amount, { from: initialHolder }, 'ERC20: approve to the zero address')
+        );
       });
     });
   });
@@ -315,16 +332,19 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
       const spender = ZERO_ADDRESS;
 
       it('reverts', async function () {
-        await shouldFail.reverting(this.token.increaseAllowance(spender, amount, { from: initialHolder }));
+        await shouldFail.reverting.withMessage(
+          this.token.increaseAllowance(spender, amount, { from: initialHolder }), 'ERC20: approve to the zero address'
+        );
       });
     });
   });
 
   describe('_mint', function () {
     const amount = new BN(50);
-
-    it('rejects a zero account', async function () {
-      await shouldFail.reverting(this.token.mint(ZERO_ADDRESS, amount));
+    it('rejects a null account', async function () {
+      await shouldFail.reverting.withMessage(
+        this.token.mint(ZERO_ADDRESS, amount), 'ERC20: mint to the zero address'
+      );
     });
 
     describe('for a non zero account', function () {
@@ -354,13 +374,16 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
   });
 
   describe('_burn', function () {
-    it('rejects a zero account', async function () {
-      await shouldFail.reverting(this.token.burn(ZERO_ADDRESS, new BN(1)));
+    it('rejects a null account', async function () {
+      await shouldFail.reverting.withMessage(this.token.burn(ZERO_ADDRESS, new BN(1)),
+        'ERC20: burn from the zero address');
     });
 
     describe('for a non zero account', function () {
       it('rejects burning more than balance', async function () {
-        await shouldFail.reverting(this.token.burn(initialHolder, initialSupply.addn(1)));
+        await shouldFail.reverting.withMessage(this.token.burn(
+          initialHolder, initialSupply.addn(1)), 'SafeMath: subtraction overflow'
+        );
       });
 
       const describeBurn = function (description, amount) {
@@ -405,17 +428,23 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
       await this.token.approve(spender, allowance, { from: initialHolder });
     });
 
-    it('rejects a zero account', async function () {
-      await shouldFail.reverting(this.token.burnFrom(ZERO_ADDRESS, new BN(1)));
+    it('rejects a null account', async function () {
+      await shouldFail.reverting.withMessage(this.token.burnFrom(ZERO_ADDRESS, new BN(1)),
+        'ERC20: burn from the zero address'
+      );
     });
 
     describe('for a non zero account', function () {
       it('rejects burning more than allowance', async function () {
-        await shouldFail.reverting(this.token.burnFrom(initialHolder, allowance.addn(1)));
+        await shouldFail.reverting.withMessage(this.token.burnFrom(initialHolder, allowance.addn(1)),
+          'SafeMath: subtraction overflow'
+        );
       });
 
       it('rejects burning more than balance', async function () {
-        await shouldFail.reverting(this.token.burnFrom(initialHolder, initialSupply.addn(1)));
+        await shouldFail.reverting.withMessage(this.token.burnFrom(initialHolder, initialSupply.addn(1)),
+          'SafeMath: subtraction overflow'
+        );
       });
 
       const describeBurnFrom = function (description, amount) {
@@ -477,7 +506,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
 
     describe('when the owner is the zero address', function () {
       it('reverts', async function () {
-        await shouldFail.reverting(this.token.approveInternal(ZERO_ADDRESS, recipient, initialSupply));
+        await shouldFail.reverting.withMessage(this.token.approveInternal(ZERO_ADDRESS, recipient, initialSupply),
+          'ERC20: approve from the zero address'
+        );
       });
     });
   });
@@ -555,7 +586,9 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
 
     describe('when the spender is the zero address', function () {
       it('reverts', async function () {
-        await shouldFail.reverting(approve.call(this, owner, ZERO_ADDRESS, supply));
+        await shouldFail.reverting.withMessage(approve.call(this, owner, ZERO_ADDRESS, supply),
+          'ERC20: approve to the zero address'
+        );
       });
     });
   }

+ 2 - 2
test/token/ERC20/ERC20Capped.test.js

@@ -8,8 +8,8 @@ contract('ERC20Capped', function ([_, minter, ...otherAccounts]) {
   const cap = ether('1000');
 
   it('requires a non-zero cap', async function () {
-    await shouldFail.reverting(
-      ERC20Capped.new(new BN(0), { from: minter })
+    await shouldFail.reverting.withMessage(
+      ERC20Capped.new(new BN(0), { from: minter }), 'ERC20Capped: cap is 0'
     );
   });
 

+ 23 - 9
test/token/ERC20/ERC20Pausable.test.js

@@ -42,7 +42,7 @@ contract('ERC20Pausable', function ([_, pauser, otherPauser, recipient, anotherA
         });
 
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.pause({ from }));
+          await shouldFail.reverting.withMessage(this.token.pause({ from }), 'Pausable: paused');
         });
       });
     });
@@ -51,7 +51,9 @@ contract('ERC20Pausable', function ([_, pauser, otherPauser, recipient, anotherA
       const from = anotherAccount;
 
       it('reverts', async function () {
-        await shouldFail.reverting(this.token.pause({ from }));
+        await shouldFail.reverting.withMessage(this.token.pause({ from }),
+          'PauserRole: caller does not have the Pauser role'
+        );
       });
     });
   });
@@ -79,7 +81,7 @@ contract('ERC20Pausable', function ([_, pauser, otherPauser, recipient, anotherA
 
       describe('when the token is unpaused', function () {
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.unpause({ from }));
+          await shouldFail.reverting.withMessage(this.token.unpause({ from }), 'Pausable: not paused');
         });
       });
     });
@@ -88,7 +90,9 @@ contract('ERC20Pausable', function ([_, pauser, otherPauser, recipient, anotherA
       const from = anotherAccount;
 
       it('reverts', async function () {
-        await shouldFail.reverting(this.token.unpause({ from }));
+        await shouldFail.reverting.withMessage(this.token.unpause({ from }),
+          'PauserRole: caller does not have the Pauser role'
+        );
       });
     });
   });
@@ -134,7 +138,9 @@ contract('ERC20Pausable', function ([_, pauser, otherPauser, recipient, anotherA
       it('reverts when trying to transfer when paused', async function () {
         await this.token.pause({ from: pauser });
 
-        await shouldFail.reverting(this.token.transfer(recipient, initialSupply, { from: pauser }));
+        await shouldFail.reverting.withMessage(this.token.transfer(recipient, initialSupply, { from: pauser }),
+          'Pausable: paused'
+        );
       });
     });
 
@@ -159,7 +165,9 @@ contract('ERC20Pausable', function ([_, pauser, otherPauser, recipient, anotherA
       it('reverts when trying to approve when paused', async function () {
         await this.token.pause({ from: pauser });
 
-        await shouldFail.reverting(this.token.approve(anotherAccount, allowance, { from: pauser }));
+        await shouldFail.reverting.withMessage(this.token.approve(anotherAccount, allowance, { from: pauser }),
+          'Pausable: paused'
+        );
       });
     });
 
@@ -190,7 +198,9 @@ contract('ERC20Pausable', function ([_, pauser, otherPauser, recipient, anotherA
       it('reverts when trying to transfer from when paused', async function () {
         await this.token.pause({ from: pauser });
 
-        await shouldFail.reverting(this.token.transferFrom(pauser, recipient, allowance, { from: anotherAccount }));
+        await shouldFail.reverting.withMessage(this.token.transferFrom(
+          pauser, recipient, allowance, { from: anotherAccount }), 'Pausable: paused'
+        );
       });
     });
 
@@ -220,7 +230,9 @@ contract('ERC20Pausable', function ([_, pauser, otherPauser, recipient, anotherA
       it('reverts when trying to transfer when paused', async function () {
         await this.token.pause({ from: pauser });
 
-        await shouldFail.reverting(this.token.decreaseAllowance(anotherAccount, decrement, { from: pauser }));
+        await shouldFail.reverting.withMessage(this.token.decreaseAllowance(
+          anotherAccount, decrement, { from: pauser }), 'Pausable: paused'
+        );
       });
     });
 
@@ -250,7 +262,9 @@ contract('ERC20Pausable', function ([_, pauser, otherPauser, recipient, anotherA
       it('reverts when trying to increase approval when paused', async function () {
         await this.token.pause({ from: pauser });
 
-        await shouldFail.reverting(this.token.increaseAllowance(anotherAccount, increment, { from: pauser }));
+        await shouldFail.reverting.withMessage(this.token.increaseAllowance(
+          anotherAccount, increment, { from: pauser }), 'Pausable: paused'
+        );
       });
     });
   });

+ 20 - 9
test/token/ERC20/SafeERC20.test.js

@@ -11,7 +11,7 @@ contract('SafeERC20', function ([_, hasNoCode]) {
       this.wrapper = await SafeERC20Wrapper.new(hasNoCode);
     });
 
-    shouldRevertOnAllCalls();
+    shouldRevertOnAllCalls('SafeERC20: call to non-contract');
   });
 
   describe('with token that returns false on all calls', function () {
@@ -19,7 +19,7 @@ contract('SafeERC20', function ([_, hasNoCode]) {
       this.wrapper = await SafeERC20Wrapper.new((await ERC20ReturnFalseMock.new()).address);
     });
 
-    shouldRevertOnAllCalls();
+    shouldRevertOnAllCalls('SafeERC20: ERC20 operation did not succeed');
   });
 
   describe('with token that returns true on all calls', function () {
@@ -39,24 +39,26 @@ contract('SafeERC20', function ([_, hasNoCode]) {
   });
 });
 
-function shouldRevertOnAllCalls () {
+function shouldRevertOnAllCalls (reason) {
   it('reverts on transfer', async function () {
-    await shouldFail.reverting(this.wrapper.transfer());
+    await shouldFail.reverting.withMessage(this.wrapper.transfer(), reason);
   });
 
   it('reverts on transferFrom', async function () {
-    await shouldFail.reverting(this.wrapper.transferFrom());
+    await shouldFail.reverting.withMessage(this.wrapper.transferFrom(), reason);
   });
 
   it('reverts on approve', async function () {
-    await shouldFail.reverting(this.wrapper.approve(0));
+    await shouldFail.reverting.withMessage(this.wrapper.approve(0), reason);
   });
 
   it('reverts on increaseAllowance', async function () {
+    // [TODO] make sure it's reverting for the right reason
     await shouldFail.reverting(this.wrapper.increaseAllowance(0));
   });
 
   it('reverts on decreaseAllowance', async function () {
+    // [TODO] make sure it's reverting for the right reason
     await shouldFail.reverting(this.wrapper.decreaseAllowance(0));
   });
 }
@@ -89,7 +91,10 @@ function shouldOnlyRevertOnErrors () {
       });
 
       it('reverts when decreasing the allowance', async function () {
-        await shouldFail.reverting(this.wrapper.decreaseAllowance(10));
+        await shouldFail.reverting.withMessage(
+          this.wrapper.decreaseAllowance(10),
+          'SafeMath: subtraction overflow'
+        );
       });
     });
 
@@ -99,7 +104,10 @@ function shouldOnlyRevertOnErrors () {
       });
 
       it('reverts when approving a non-zero allowance', async function () {
-        await shouldFail.reverting(this.wrapper.approve(20));
+        await shouldFail.reverting.withMessage(
+          this.wrapper.approve(20),
+          'SafeERC20: approve from non-zero to non-zero allowance'
+        );
       });
 
       it('doesn\'t revert when approving a zero allowance', async function () {
@@ -115,7 +123,10 @@ function shouldOnlyRevertOnErrors () {
       });
 
       it('reverts when decreasing the allowance to a negative value', async function () {
-        await shouldFail.reverting(this.wrapper.decreaseAllowance(200));
+        await shouldFail.reverting.withMessage(
+          this.wrapper.decreaseAllowance(200),
+          'SafeMath: subtraction overflow'
+        );
       });
     });
   });

+ 4 - 4
test/token/ERC20/TokenTimelock.test.js

@@ -13,7 +13,7 @@ contract('TokenTimelock', function ([_, minter, beneficiary]) {
 
     it('rejects a release time in the past', async function () {
       const pastReleaseTime = (await time.latest()).sub(time.duration.years(1));
-      await shouldFail.reverting(
+      await shouldFail.reverting.withMessage(
         TokenTimelock.new(this.token.address, beneficiary, pastReleaseTime)
       );
     });
@@ -32,12 +32,12 @@ contract('TokenTimelock', function ([_, minter, beneficiary]) {
       });
 
       it('cannot be released before time limit', async function () {
-        await shouldFail.reverting(this.timelock.release());
+        await shouldFail.reverting.withMessage(this.timelock.release());
       });
 
       it('cannot be released just before time limit', async function () {
         await time.increaseTo(this.releaseTime.sub(time.duration.seconds(3)));
-        await shouldFail.reverting(this.timelock.release());
+        await shouldFail.reverting.withMessage(this.timelock.release());
       });
 
       it('can be released just after limit', async function () {
@@ -55,7 +55,7 @@ contract('TokenTimelock', function ([_, minter, beneficiary]) {
       it('cannot be released twice', async function () {
         await time.increaseTo(this.releaseTime.add(time.duration.years(1)));
         await this.timelock.release();
-        await shouldFail.reverting(this.timelock.release());
+        await shouldFail.reverting.withMessage(this.timelock.release());
         (await this.token.balanceOf(beneficiary)).should.be.bignumber.equal(amount);
       });
     });

+ 9 - 3
test/token/ERC20/behaviors/ERC20Burnable.behavior.js

@@ -35,7 +35,9 @@ function shouldBehaveLikeERC20Burnable (owner, initialBalance, [burner]) {
       const amount = initialBalance.addn(1);
 
       it('reverts', async function () {
-        await shouldFail.reverting(this.token.burn(amount, { from: owner }));
+        await shouldFail.reverting.withMessage(this.token.burn(amount, { from: owner }),
+          'SafeMath: subtraction overflow'
+        );
       });
     });
   });
@@ -82,7 +84,9 @@ function shouldBehaveLikeERC20Burnable (owner, initialBalance, [burner]) {
 
       it('reverts', async function () {
         await this.token.approve(burner, amount, { from: owner });
-        await shouldFail.reverting(this.token.burnFrom(owner, amount, { from: burner }));
+        await shouldFail.reverting.withMessage(this.token.burnFrom(owner, amount, { from: burner }),
+          'SafeMath: subtraction overflow'
+        );
       });
     });
 
@@ -91,7 +95,9 @@ function shouldBehaveLikeERC20Burnable (owner, initialBalance, [burner]) {
 
       it('reverts', async function () {
         await this.token.approve(burner, allowance, { from: owner });
-        await shouldFail.reverting(this.token.burnFrom(owner, allowance.addn(1), { from: burner }));
+        await shouldFail.reverting.withMessage(this.token.burnFrom(owner, allowance.addn(1), { from: burner }),
+          'SafeMath: subtraction overflow'
+        );
       });
     });
   });

+ 2 - 2
test/token/ERC20/behaviors/ERC20Capped.behavior.js

@@ -15,12 +15,12 @@ function shouldBehaveLikeERC20Capped (minter, [other], cap) {
 
     it('should fail to mint if the amount exceeds the cap', async function () {
       await this.token.mint(other, cap.subn(1), { from });
-      await shouldFail.reverting(this.token.mint(other, 2, { from }));
+      await shouldFail.reverting.withMessage(this.token.mint(other, 2, { from }));
     });
 
     it('should fail to mint after cap is reached', async function () {
       await this.token.mint(other, cap, { from });
-      await shouldFail.reverting(this.token.mint(other, 1, { from }));
+      await shouldFail.reverting.withMessage(this.token.mint(other, 1, { from }));
     });
   });
 }

+ 3 - 1
test/token/ERC20/behaviors/ERC20Mintable.behavior.js

@@ -40,7 +40,9 @@ function shouldBehaveLikeERC20Mintable (minter, [other]) {
         const from = other;
 
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.mint(other, amount, { from }));
+          await shouldFail.reverting.withMessage(this.token.mint(other, amount, { from }),
+            'MinterRole: caller does not have the Minter role'
+          );
         });
       });
     });

+ 40 - 21
test/token/ERC721/ERC721.behavior.js

@@ -36,7 +36,9 @@ function shouldBehaveLikeERC721 (
 
       context('when querying the zero address', function () {
         it('throws', async function () {
-          await shouldFail.reverting(this.token.balanceOf(ZERO_ADDRESS));
+          await shouldFail.reverting.withMessage(
+            this.token.balanceOf(ZERO_ADDRESS), 'ERC721: balance query for the zero address'
+          );
         });
       });
     });
@@ -54,7 +56,9 @@ function shouldBehaveLikeERC721 (
         const tokenId = unknownTokenId;
 
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.ownerOf(tokenId));
+          await shouldFail.reverting.withMessage(
+            this.token.ownerOf(tokenId), 'ERC721: owner query for nonexistent token'
+          );
         });
       });
     });
@@ -178,29 +182,36 @@ function shouldBehaveLikeERC721 (
 
         context('when the address of the previous owner is incorrect', function () {
           it('reverts', async function () {
-            await shouldFail.reverting(transferFunction.call(this, other, other, tokenId, { from: owner })
+            await shouldFail.reverting.withMessage(
+              transferFunction.call(this, other, other, tokenId, { from: owner }),
+              'ERC721: transfer of token that is not own'
             );
           });
         });
 
         context('when the sender is not authorized for the token id', function () {
           it('reverts', async function () {
-            await shouldFail.reverting(transferFunction.call(this, owner, other, tokenId, { from: other })
+            await shouldFail.reverting.withMessage(
+              transferFunction.call(this, owner, other, tokenId, { from: other }),
+              'ERC721: transfer caller is not owner nor approved'
             );
           });
         });
 
         context('when the given token ID does not exist', function () {
           it('reverts', async function () {
-            await shouldFail.reverting(transferFunction.call(this, owner, other, unknownTokenId, { from: owner })
+            await shouldFail.reverting.withMessage(
+              transferFunction.call(this, owner, other, unknownTokenId, { from: owner }),
+              'ERC721: operator query for nonexistent token'
             );
           });
         });
 
         context('when the address to transfer the token to is the zero address', function () {
           it('reverts', async function () {
-            await shouldFail.reverting(
-              transferFunction.call(this, owner, ZERO_ADDRESS, tokenId, { from: owner })
+            await shouldFail.reverting.withMessage(
+              transferFunction.call(this, owner, ZERO_ADDRESS, tokenId, { from: owner }),
+              'ERC721: transfer to the zero address'
             );
           });
         });
@@ -258,14 +269,15 @@ function shouldBehaveLikeERC721 (
 
             describe('with an invalid token id', function () {
               it('reverts', async function () {
-                await shouldFail.reverting(
+                await shouldFail.reverting.withMessage(
                   transferFun.call(
                     this,
                     owner,
                     this.receiver.address,
                     unknownTokenId,
                     { from: owner },
-                  )
+                  ),
+                  'ERC721: operator query for nonexistent token'
                 );
               });
             });
@@ -283,8 +295,9 @@ function shouldBehaveLikeERC721 (
         describe('to a receiver contract returning unexpected value', function () {
           it('reverts', async function () {
             const invalidReceiver = await ERC721ReceiverMock.new('0x42', false);
-            await shouldFail.reverting(
-              this.token.safeTransferFrom(owner, invalidReceiver.address, tokenId, { from: owner })
+            await shouldFail.reverting.withMessage(
+              this.token.safeTransferFrom(owner, invalidReceiver.address, tokenId, { from: owner }),
+              'ERC721: transfer to non ERC721Receiver implementer'
             );
           });
         });
@@ -292,8 +305,9 @@ function shouldBehaveLikeERC721 (
         describe('to a receiver contract that throws', function () {
           it('reverts', async function () {
             const invalidReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, true);
-            await shouldFail.reverting(
-              this.token.safeTransferFrom(owner, invalidReceiver.address, tokenId, { from: owner })
+            await shouldFail.reverting.withMessage(
+              this.token.safeTransferFrom(owner, invalidReceiver.address, tokenId, { from: owner }),
+              'ERC721ReceiverMock: reverting'
             );
           });
         });
@@ -301,8 +315,9 @@ function shouldBehaveLikeERC721 (
         describe('to a contract that does not implement the required function', function () {
           it('reverts', async function () {
             const invalidReceiver = this.token;
-            await shouldFail.reverting(
-              this.token.safeTransferFrom(owner, invalidReceiver.address, tokenId, { from: owner })
+            await shouldFail.reverting.withMessage(
+              this.token.safeTransferFrom(owner, invalidReceiver.address, tokenId, { from: owner }),
+              'VM Exception while processing transaction: revert'
             );
           });
         });
@@ -390,22 +405,24 @@ function shouldBehaveLikeERC721 (
 
       context('when the address that receives the approval is the owner', function () {
         it('reverts', async function () {
-          await shouldFail.reverting(
-            this.token.approve(owner, tokenId, { from: owner })
+          await shouldFail.reverting.withMessage(
+            this.token.approve(owner, tokenId, { from: owner }), 'ERC721: transfer to current owner'
           );
         });
       });
 
       context('when the sender does not own the given token ID', function () {
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.approve(approved, tokenId, { from: other }));
+          await shouldFail.reverting.withMessage(this.token.approve(approved, tokenId, { from: other }),
+            'ERC721: approve caller is not owner nor approved');
         });
       });
 
       context('when the sender is approved for the given token ID', function () {
         it('reverts', async function () {
           await this.token.approve(approved, tokenId, { from: owner });
-          await shouldFail.reverting(this.token.approve(anotherApproved, tokenId, { from: approved }));
+          await shouldFail.reverting.withMessage(this.token.approve(anotherApproved, tokenId, { from: approved }),
+            'ERC721: approve caller is not owner nor approved for all');
         });
       });
 
@@ -421,7 +438,8 @@ function shouldBehaveLikeERC721 (
 
       context('when the given token ID does not exist', function () {
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.approve(approved, unknownTokenId, { from: operator }));
+          await shouldFail.reverting.withMessage(this.token.approve(approved, unknownTokenId, { from: operator }),
+            'ERC721: owner query for nonexistent token');
         });
       });
     });
@@ -499,7 +517,8 @@ function shouldBehaveLikeERC721 (
 
       context('when the operator is the owner', function () {
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.setApprovalForAll(owner, true, { from: owner }));
+          await shouldFail.reverting.withMessage(this.token.setApprovalForAll(owner, true, { from: owner }),
+            'ERC721: approve to caller');
         });
       });
     });

+ 27 - 10
test/token/ERC721/ERC721.test.js

@@ -15,8 +15,10 @@ contract('ERC721', function ([_, creator, tokenOwner, other, ...accounts]) {
     const tokenId = new BN('5042');
 
     describe('_mint(address, uint256)', function () {
-      it('reverts with a zero destination address', async function () {
-        await shouldFail.reverting(this.token.mint(ZERO_ADDRESS, tokenId));
+      it('reverts with a null destination address', async function () {
+        await shouldFail.reverting.withMessage(
+          this.token.mint(ZERO_ADDRESS, tokenId), 'ERC721: mint to the zero address'
+        );
       });
 
       context('with minted token', async function () {
@@ -34,14 +36,16 @@ contract('ERC721', function ([_, creator, tokenOwner, other, ...accounts]) {
         });
 
         it('reverts when adding a token id that already exists', async function () {
-          await shouldFail.reverting(this.token.mint(tokenOwner, tokenId));
+          await shouldFail.reverting.withMessage(this.token.mint(tokenOwner, tokenId), 'ERC721: token already minted');
         });
       });
     });
 
     describe('_burn(address, uint256)', function () {
       it('reverts when burning a non-existent token id', async function () {
-        await shouldFail.reverting(this.token.methods['burn(address,uint256)'](tokenOwner, tokenId));
+        await shouldFail.reverting.withMessage(
+          this.token.methods['burn(address,uint256)'](tokenOwner, tokenId), 'ERC721: owner query for nonexistent token'
+        );
       });
 
       context('with minted token', function () {
@@ -50,7 +54,9 @@ contract('ERC721', function ([_, creator, tokenOwner, other, ...accounts]) {
         });
 
         it('reverts when the account is not the owner', async function () {
-          await shouldFail.reverting(this.token.methods['burn(address,uint256)'](other, tokenId));
+          await shouldFail.reverting.withMessage(
+            this.token.methods['burn(address,uint256)'](other, tokenId), 'ERC721: burn of token that is not own'
+          );
         });
 
         context('with burnt token', function () {
@@ -64,11 +70,16 @@ contract('ERC721', function ([_, creator, tokenOwner, other, ...accounts]) {
 
           it('deletes the token', async function () {
             (await this.token.balanceOf(tokenOwner)).should.be.bignumber.equal('0');
-            await shouldFail.reverting(this.token.ownerOf(tokenId));
+            await shouldFail.reverting.withMessage(
+              this.token.ownerOf(tokenId), 'ERC721: owner query for nonexistent token'
+            );
           });
 
           it('reverts when burning a token id that has been deleted', async function () {
-            await shouldFail.reverting(this.token.methods['burn(address,uint256)'](tokenOwner, tokenId));
+            await shouldFail.reverting.withMessage(
+              this.token.methods['burn(address,uint256)'](tokenOwner, tokenId),
+              'ERC721: owner query for nonexistent token'
+            );
           });
         });
       });
@@ -76,7 +87,9 @@ contract('ERC721', function ([_, creator, tokenOwner, other, ...accounts]) {
 
     describe('_burn(uint256)', function () {
       it('reverts when burning a non-existent token id', async function () {
-        await shouldFail.reverting(this.token.methods['burn(uint256)'](tokenId));
+        await shouldFail.reverting.withMessage(
+          this.token.methods['burn(uint256)'](tokenId), 'ERC721: owner query for nonexistent token'
+        );
       });
 
       context('with minted token', function () {
@@ -95,11 +108,15 @@ contract('ERC721', function ([_, creator, tokenOwner, other, ...accounts]) {
 
           it('deletes the token', async function () {
             (await this.token.balanceOf(tokenOwner)).should.be.bignumber.equal('0');
-            await shouldFail.reverting(this.token.ownerOf(tokenId));
+            await shouldFail.reverting.withMessage(
+              this.token.ownerOf(tokenId), 'ERC721: owner query for nonexistent token'
+            );
           });
 
           it('reverts when burning a token id that has been deleted', async function () {
-            await shouldFail.reverting(this.token.methods['burn(uint256)'](tokenId));
+            await shouldFail.reverting.withMessage(
+              this.token.methods['burn(uint256)'](tokenId), 'ERC721: owner query for nonexistent token'
+            );
           });
         });
       });

+ 21 - 7
test/token/ERC721/ERC721Full.test.js

@@ -64,7 +64,9 @@ contract('ERC721Full', function ([
       it('burns all tokens', async function () {
         await this.token.burn(secondTokenId, { from: owner });
         (await this.token.totalSupply()).should.be.bignumber.equal('0');
-        await shouldFail.reverting(this.token.tokenByIndex(0));
+        await shouldFail.reverting.withMessage(
+          this.token.tokenByIndex(0), 'ERC721Enumerable: global index out of bounds'
+        );
       });
     });
 
@@ -85,7 +87,9 @@ contract('ERC721Full', function ([
       });
 
       it('reverts when setting metadata for non existent token id', async function () {
-        await shouldFail.reverting(this.token.setTokenURI(nonExistentTokenId, sampleUri));
+        await shouldFail.reverting.withMessage(
+          this.token.setTokenURI(nonExistentTokenId, sampleUri), 'ERC721Metadata: URI set of nonexistent token'
+        );
       });
 
       it('can burn token with metadata', async function () {
@@ -99,7 +103,9 @@ contract('ERC721Full', function ([
       });
 
       it('reverts when querying metadata for non existent token id', async function () {
-        await shouldFail.reverting(this.token.tokenURI(nonExistentTokenId));
+        await shouldFail.reverting.withMessage(
+          this.token.tokenURI(nonExistentTokenId), 'ERC721Metadata: URI query for nonexistent token'
+        );
       });
     });
 
@@ -127,13 +133,17 @@ contract('ERC721Full', function ([
 
       describe('when the index is greater than or equal to the total tokens owned by the given address', function () {
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.tokenOfOwnerByIndex(owner, 2));
+          await shouldFail.reverting.withMessage(
+            this.token.tokenOfOwnerByIndex(owner, 2), 'ERC721Enumerable: owner index out of bounds'
+          );
         });
       });
 
       describe('when the given address does not own any token', function () {
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.tokenOfOwnerByIndex(another, 0));
+          await shouldFail.reverting.withMessage(
+            this.token.tokenOfOwnerByIndex(another, 0), 'ERC721Enumerable: owner index out of bounds'
+          );
         });
       });
 
@@ -153,7 +163,9 @@ contract('ERC721Full', function ([
 
         it('returns empty collection for original owner', async function () {
           (await this.token.balanceOf(owner)).should.be.bignumber.equal('0');
-          await shouldFail.reverting(this.token.tokenOfOwnerByIndex(owner, 0));
+          await shouldFail.reverting.withMessage(
+            this.token.tokenOfOwnerByIndex(owner, 0), 'ERC721Enumerable: owner index out of bounds'
+          );
         });
       });
     });
@@ -167,7 +179,9 @@ contract('ERC721Full', function ([
       });
 
       it('should revert if index is greater than supply', async function () {
-        await shouldFail.reverting(this.token.tokenByIndex(2));
+        await shouldFail.reverting.withMessage(
+          this.token.tokenByIndex(2), 'ERC721Enumerable: global index out of bounds'
+        );
       });
 
       [firstTokenId, secondTokenId].forEach(function (tokenId) {

+ 16 - 6
test/token/ERC721/ERC721MintBurn.behavior.js

@@ -46,13 +46,18 @@ function shouldBehaveLikeMintAndBurnERC721 (
 
       describe('when the given owner address is the zero address', function () {
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.mint(ZERO_ADDRESS, thirdTokenId, { from: minter }));
+          await shouldFail.reverting.withMessage(
+            this.token.mint(ZERO_ADDRESS, thirdTokenId, { from: minter }),
+            'ERC721: mint to the zero address'
+          );
         });
       });
 
       describe('when the given token ID was already tracked by this contract', function () {
         it('reverts', async function () {
-          await shouldFail.reverting(this.token.mint(owner, firstTokenId, { from: minter }));
+          await shouldFail.reverting.withMessage(this.token.mint(owner, firstTokenId, { from: minter }),
+            'ERC721: token already minted.'
+          );
         });
       });
     });
@@ -76,7 +81,10 @@ function shouldBehaveLikeMintAndBurnERC721 (
         });
 
         it('burns the given token ID and adjusts the balance of the owner', async function () {
-          await shouldFail.reverting(this.token.ownerOf(tokenId));
+          await shouldFail.reverting.withMessage(
+            this.token.ownerOf(tokenId),
+            'ERC721: owner query for nonexistent token'
+          );
           (await this.token.balanceOf(owner)).should.be.bignumber.equal('1');
         });
 
@@ -98,15 +106,17 @@ function shouldBehaveLikeMintAndBurnERC721 (
 
         context('getApproved', function () {
           it('reverts', async function () {
-            await shouldFail.reverting(this.token.getApproved(tokenId));
+            await shouldFail.reverting.withMessage(
+              this.token.getApproved(tokenId), 'ERC721: approved query for nonexistent token'
+            );
           });
         });
       });
 
       describe('when the given token ID was not tracked by this contract', function () {
         it('reverts', async function () {
-          await shouldFail.reverting(
-            this.token.burn(unknownTokenId, { from: creator })
+          await shouldFail.reverting.withMessage(
+            this.token.burn(unknownTokenId, { from: creator }), 'ERC721: operator query for nonexistent token'
           );
         });
       });

+ 16 - 6
test/token/ERC721/ERC721PausedToken.behavior.js

@@ -12,24 +12,34 @@ function shouldBehaveLikeERC721PausedToken (owner, [recipient, operator]) {
     });
 
     it('reverts when trying to approve', async function () {
-      await shouldFail.reverting(this.token.approve(recipient, firstTokenId, { from: owner }));
+      await shouldFail.reverting.withMessage(
+        this.token.approve(recipient, firstTokenId, { from: owner }), 'Pausable: paused'
+      );
     });
 
     it('reverts when trying to setApprovalForAll', async function () {
-      await shouldFail.reverting(this.token.setApprovalForAll(operator, true, { from: owner }));
+      await shouldFail.reverting.withMessage(
+        this.token.setApprovalForAll(operator, true, { from: owner }), 'Pausable: paused'
+      );
     });
 
     it('reverts when trying to transferFrom', async function () {
-      await shouldFail.reverting(this.token.transferFrom(owner, recipient, firstTokenId, { from: owner }));
+      await shouldFail.reverting.withMessage(
+        this.token.transferFrom(owner, recipient, firstTokenId, { from: owner }), 'Pausable: paused'
+      );
     });
 
     it('reverts when trying to safeTransferFrom', async function () {
-      await shouldFail.reverting(this.token.safeTransferFrom(owner, recipient, firstTokenId, { from: owner }));
+      await shouldFail.reverting.withMessage(
+        this.token.safeTransferFrom(owner, recipient, firstTokenId, { from: owner }), 'Pausable: paused'
+      );
     });
 
     it('reverts when trying to safeTransferFrom with data', async function () {
-      await shouldFail.reverting(this.token.methods['safeTransferFrom(address,address,uint256,bytes)'](
-        owner, recipient, firstTokenId, mockData, { from: owner })
+      await shouldFail.reverting.withMessage(
+        this.token.methods['safeTransferFrom(address,address,uint256,bytes)'](
+          owner, recipient, firstTokenId, mockData, { from: owner }
+        ), 'Pausable: paused'
       );
     });
 

+ 8 - 3
test/utils/ReentrancyGuard.test.js

@@ -11,7 +11,8 @@ contract('ReentrancyGuard', function () {
 
   it('should not allow remote callback', async function () {
     const attacker = await ReentrancyAttack.new();
-    await shouldFail.reverting(this.reentrancyMock.countAndCall(attacker.address));
+    await shouldFail.reverting.withMessage(
+      this.reentrancyMock.countAndCall(attacker.address), 'ReentrancyGuard: reentrant call');
   });
 
   // The following are more side-effects than intended behavior:
@@ -19,10 +20,14 @@ contract('ReentrancyGuard', function () {
   // in the side-effects.
 
   it('should not allow local recursion', async function () {
-    await shouldFail.reverting(this.reentrancyMock.countLocalRecursive(10));
+    await shouldFail.reverting.withMessage(
+      this.reentrancyMock.countLocalRecursive(10), 'ReentrancyGuard: reentrant call'
+    );
   });
 
   it('should not allow indirect local recursion', async function () {
-    await shouldFail.reverting(this.reentrancyMock.countThisRecursive(10));
+    await shouldFail.reverting.withMessage(
+      this.reentrancyMock.countThisRecursive(10), 'ReentrancyMock: failed call'
+    );
   });
 });