Просмотр исходного кода

Merge branch 'release-v3.2.0' into release-v3.2.0-solc-0.7

Francisco Giordano 5 лет назад
Родитель
Сommit
22de765f3c
70 измененных файлов с 2317 добавлено и 522 удалено
  1. 4 1
      .editorconfig
  2. 3 0
      .mocharc.js
  3. 2 1
      .solhint.json
  4. 14 0
      CHANGELOG.md
  5. 0 69
      CODE_STYLE.md
  6. 1 1
      DOCUMENTATION.md
  7. 58 17
      GUIDELINES.md
  8. 2 2
      README.md
  9. 2 2
      contracts/GSN/GSNRecipient.sol
  10. 2 2
      contracts/GSN/IRelayHub.sol
  11. 1 1
      contracts/GSN/IRelayRecipient.sol
  12. 1 1
      contracts/math/SignedSafeMath.sol
  13. 20 0
      contracts/mocks/ClashingImplementation.sol
  14. 57 0
      contracts/mocks/DummyImplementation.sol
  15. 36 0
      contracts/mocks/InitializableMock.sol
  16. 76 0
      contracts/mocks/MultipleInheritanceInitializableMocks.sol
  17. 66 0
      contracts/mocks/RegressionImplementation.sol
  18. 49 0
      contracts/mocks/SingleInheritanceInitializableMocks.sol
  19. 1 1
      contracts/package.json
  20. 1 1
      contracts/presets/ERC721PresetMinterPauserAutoId.sol
  21. 62 0
      contracts/proxy/Initializable.sol
  22. 83 0
      contracts/proxy/Proxy.sol
  23. 77 0
      contracts/proxy/ProxyAdmin.sol
  24. 26 0
      contracts/proxy/README.adoc
  25. 153 0
      contracts/proxy/TransparentUpgradeableProxy.sol
  26. 80 0
      contracts/proxy/UpgradeableProxy.sol
  27. 4 4
      contracts/token/ERC1155/ERC1155.sol
  28. 1 1
      contracts/token/ERC1155/IERC1155.sol
  29. 2 2
      contracts/token/ERC20/ERC20.sol
  30. 15 18
      contracts/token/ERC20/ERC20Snapshot.sol
  31. 1 1
      contracts/token/ERC20/README.adoc
  32. 1 1
      contracts/token/ERC721/ERC721.sol
  33. 1 1
      contracts/token/ERC721/IERC721.sol
  34. 1 1
      contracts/token/ERC721/README.adoc
  35. 1 1
      contracts/token/ERC777/README.adoc
  36. 1 1
      contracts/utils/README.adoc
  37. 1 1
      docs/modules/ROOT/pages/access-control.adoc
  38. 2 2
      docs/modules/ROOT/pages/erc1155.adoc
  39. 1 1
      docs/modules/ROOT/pages/index.adoc
  40. 2 2
      docs/modules/ROOT/pages/releases-stability.adoc
  41. BIN
      logo.png
  42. 5 0
      logo.svg
  43. 1 1
      package-lock.json
  44. 1 1
      package.json
  45. 34 30
      test/access/Ownable.test.js
  46. 3 3
      test/cryptography/MerkleProof.test.js
  47. 1 1
      test/helpers/sign.js
  48. 3 3
      test/introspection/SupportsInterface.behavior.js
  49. 23 20
      test/payment/PaymentSplitter.test.js
  50. 29 25
      test/payment/PullPayment.test.js
  51. 81 0
      test/proxy/Initializable.test.js
  52. 119 0
      test/proxy/ProxyAdmin.test.js
  53. 436 0
      test/proxy/TransparentUpgradeableProxy.behaviour.js
  54. 17 0
      test/proxy/TransparentUpgradeableProxy.test.js
  55. 216 0
      test/proxy/UpgradeableProxy.behaviour.js
  56. 15 0
      test/proxy/UpgradeableProxy.test.js
  57. 74 6
      test/token/ERC1155/ERC1155.behavior.js
  58. 26 1
      test/token/ERC1155/ERC1155.test.js
  59. 32 21
      test/token/ERC1155/ERC1155Holder.test.js
  60. 1 1
      test/token/ERC20/ERC20Snapshot.test.js
  61. 4 4
      test/token/ERC20/behaviors/ERC20Capped.behavior.js
  62. 8 8
      test/token/ERC721/ERC721.test.js
  63. 1 1
      test/token/ERC721/ERC721Pausable.test.js
  64. 2 2
      test/utils/Address.test.js
  65. 56 54
      test/utils/Arrays.test.js
  66. 30 26
      test/utils/Counters.test.js
  67. 64 63
      test/utils/Create2.test.js
  68. 61 57
      test/utils/EnumerableMap.test.js
  69. 60 54
      test/utils/EnumerableSet.behavior.js
  70. 3 4
      test/utils/ReentrancyGuard.test.js

+ 4 - 1
.editorconfig

@@ -8,7 +8,7 @@ charset = utf-8
 end_of_line = lf
 indent_style = space
 insert_final_newline = true
-trim_trailing_whitespace = true
+trim_trailing_whitespace = false
 max_line_length = 120
 
 [*.sol]
@@ -16,3 +16,6 @@ indent_size = 4
 
 [*.js]
 indent_size = 2
+
+[*.adoc]
+max_line_length = 0

+ 3 - 0
.mocharc.js

@@ -0,0 +1,3 @@
+module.exports = {
+  timeout: 4000,
+};

+ 2 - 1
.solhint.json

@@ -5,6 +5,7 @@
     "mark-callable-contracts": "off",
     "no-empty-blocks": "off",
     "compiler-version": ["error", "^0.7.0"],
-    "private-vars-leading-underscore": "error"
+    "private-vars-leading-underscore": "error",
+    "reason-string": "off"
   }
 }

+ 14 - 0
CHANGELOG.md

@@ -1,5 +1,19 @@
 # Changelog
 
+## 3.2.0 (unreleased)
+
+### New features
+ * Proxies: added the proxy contracts from OpenZeppelin SDK. ([#2335](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2335))
+
+### Improvements
+ * `Address.isContract`: switched from `extcodehash` to `extcodesize` for less gas usage. ([#2311](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2311))
+
+### Breaking changes
+ * `ERC20Snapshot`: switched to using `_beforeTokenTransfer` hook instead of overriding ERC20 operations. ([#2312](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2312))
+
+This small change in the way we implemented `ERC20Snapshot` may affect users who are combining this contract with
+other ERC20 flavors, since it no longer overrides `_transfer`, `_mint`, and `_burn`. This can result in having to remove Solidity `override(...)` specifiers in derived contracts for these functions, and to instead have to add it for `_beforeTokenTransfer`. See [Using Hooks](https://docs.openzeppelin.com/contracts/3.x/extending-contracts#using-hooks) in the documentation.
+
 ## 3.1.0 (2020-06-23)
 
 ### New features

+ 0 - 69
CODE_STYLE.md

@@ -1,69 +0,0 @@
-# Code Style
-
-We value clean code and consistency, and those are prerequisites for us to
-include new code in the repository. Before proposing a change, please read this
-document and take some time to familiarize yourself with the style of the
-existing codebase.
-
-## Solidity code
-
-In order to be consistent with all the other Solidity projects, we follow the
-[official recommendations documented in the Solidity style guide](http://solidity.readthedocs.io/en/latest/style-guide.html).
-
-Any exception or additions specific to our project are documented below.
-
-### Naming
-
-* Try to avoid acronyms and abbreviations.
-
-* All state variables should be private.
-
-* Private state variables should have an underscore prefix.
-
-    ```
-    contract TestContract {
-      uint256 private _privateVar;
-      uint256 internal _internalVar;
-    }
-    ```
-
-* Parameters must not be prefixed with an underscore.
-
-    ```
-    function test(uint256 testParameter1, uint256 testParameter2) {
-    ...
-    }
-    ```
-
-* Internal and private functions should have an underscore prefix.
-
-    ```
-    function _testInternal() internal {
-      ...
-    }
-    ```
-
-    ```
-    function _testPrivate() private {
-      ...
-    }
-    ```
-
-* Events should be emitted immediately after the state change that they
-  represent, and consequently they should be named in past tense.
-
-    ```
-    function _burn(address _who, uint256 _value) internal {
-      super._burn(_who, _value);
-      emit TokensBurned(_who, _value);
-    }
-    ```
-
-  Some standards (e.g. ERC20) use present tense, and in those cases the
-  standard specification prevails.
-  
-* Interface names should have a capital I prefix.
-
-    ```
-    interface IERC777 {
-    ```

+ 1 - 1
DOCUMENTATION.md

@@ -10,7 +10,7 @@ program that extracts the API Reference from source code.
 
 The [`docs.openzeppelin.com`](https://github.com/OpenZeppelin/docs.openzeppelin.com)
 repository hosts the configuration for the entire site, which includes
-documetation for all of the OpenZeppelin projects.
+documentation for all of the OpenZeppelin projects.
 
 To run the docs locally you should run `npm run docs:watch` on this
 repository.

+ 58 - 17
GUIDELINES.md

@@ -28,37 +28,78 @@ Consistency on the way classes are used is paramount to an easier understanding
 #### D6 - Regular Audits
 Following good programming practices is a way to reduce the risk of vulnerabilities, but professional code audits are still needed. We will perform regular code audits on major releases, and hire security professionals to provide independent review.
 
-## Style Guidelines
+# Style Guidelines
 
-The design guidelines have quite a high abstraction level. These style guidelines are more concrete and easier to apply, and also more opinionated.
+The design guidelines have quite a high abstraction level. These style guidelines are more concrete and easier to apply, and also more opinionated. We value clean code and consistency, and those are prerequisites for us to include new code in the repository. Before proposing a change, please read these guidelines and take some time to familiarize yourself with the style of the existing codebase.
 
-### General
+## Solidity code
 
-#### G0 - Default to Solidity's official style guide.
+In order to be consistent with all the other Solidity projects, we follow the
+[official recommendations documented in the Solidity style guide](http://solidity.readthedocs.io/en/latest/style-guide.html).
 
-Follow the official Solidity style guide: https://solidity.readthedocs.io/en/latest/style-guide.html
+Any exception or additions specific to our project are documented below.
 
-#### G1 - No Magic Constants
+* Try to avoid acronyms and abbreviations.
 
-Avoid constants in the code as much as possible. Magic strings are also magic constants.
+* All state variables should be private.
 
-#### G2 - Code that Fails Early
+* Private state variables should have an underscore prefix.
 
-We ask our code to fail as soon as possible when an unexpected input was provided or unexpected state was found.
+    ```
+    contract TestContract {
+      uint256 private _privateVar;
+      uint256 internal _internalVar;
+    }
+    ```
 
-#### G3 - Internal Amounts Must be Signed Integers and Represent the Smallest Units.
+* Parameters must not be prefixed with an underscore.
 
-Avoid representation errors by always dealing with weis when handling ether. GUIs can convert to more human-friendly representations. Use Signed Integers (int) to prevent underflow problems.
+    ```
+    function test(uint256 testParameter1, uint256 testParameter2) {
+    ...
+    }
+    ```
 
+* Internal and private functions should have an underscore prefix.
 
-### Testing
+    ```
+    function _testInternal() internal {
+      ...
+    }
+    ```
 
-#### T1 - Tests Must be Written Elegantly
+    ```
+    function _testPrivate() private {
+      ...
+    }
+    ```
 
-Style guidelines are not relaxed for tests. Tests are a good way to show how to use the library, and maintaining them is extremely necessary.
+* Events should be emitted immediately after the state change that they
+  represent, and consequently they should be named in past tense.
 
-Don't write long tests, write helper functions to make them be as short and concise as possible (they should take just a few lines each), and use good variable names.
+    ```
+    function _burn(address who, uint256 value) internal {
+      super._burn(who, value);
+      emit TokensBurned(who, value);
+    }
+    ```
 
-#### T2 - Tests Must not be Random
+  Some standards (e.g. ERC20) use present tense, and in those cases the
+  standard specification prevails.
+  
+* Interface names should have a capital I prefix.
 
-Inputs for tests should not be generated randomly. Accounts used to create test contracts are an exception, those can be random. Also, the type and structure of outputs should be checked.
+    ```
+    interface IERC777 {
+    ```
+
+
+## Tests
+
+* Tests Must be Written Elegantly
+
+    Tests are a good way to show how to use the library, and maintaining them is extremely necessary. Don't write long tests, write helper functions to make them be as short and concise as possible (they should take just a few lines each), and use good variable names.
+
+* Tests Must not be Random
+
+    Inputs for tests should not be generated randomly. Accounts used to create test contracts are an exception, those can be random. Also, the type and structure of outputs should be checked.

+ 2 - 2
README.md

@@ -1,4 +1,4 @@
-# <img src="logo.png" alt="OpenZeppelin" height="40px">
+# <img src="logo.svg" alt="OpenZeppelin" height="40px">
 
 [![Docs](https://img.shields.io/badge/docs-%F0%9F%93%84-blue)](https://docs.openzeppelin.com/contracts)
 [![NPM Package](https://img.shields.io/npm/v/@openzeppelin/contracts.svg)](https://www.npmjs.org/package/@openzeppelin/contracts)
@@ -44,7 +44,7 @@ To keep your system secure, you should **always** use the installed code as-is,
 
 ## Learn More
 
-The guides in the sidebar will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides:
+The guides in the [docs site](https://docs.openzeppelin.com/contracts) will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides:
 
 * [Access Control](https://docs.openzeppelin.com/contracts/access-control): decide who can perform each of the actions on your system.
 * [Tokens](https://docs.openzeppelin.com/contracts/tokens): create tradeable assets or collectives, and distribute them via [Crowdsales](https://docs.openzeppelin.com/contracts/crowdsales).

+ 2 - 2
contracts/GSN/GSNRecipient.sol

@@ -115,7 +115,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
     /**
      * @dev See `IRelayRecipient.preRelayedCall`.
      *
-     * This function should not be overriden directly, use `_preRelayedCall` instead.
+     * This function should not be overridden directly, use `_preRelayedCall` instead.
      *
      * * Requirements:
      *
@@ -138,7 +138,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
     /**
      * @dev See `IRelayRecipient.postRelayedCall`.
      *
-     * This function should not be overriden directly, use `_postRelayedCall` instead.
+     * This function should not be overridden directly, use `_postRelayedCall` instead.
      *
      * * Requirements:
      *

+ 2 - 2
contracts/GSN/IRelayHub.sol

@@ -41,7 +41,7 @@ interface IRelayHub {
     function registerRelay(uint256 transactionFee, string calldata url) external;
 
     /**
-     * @dev Emitted when a relay is registered or re-registerd. Looking at these events (and filtering out
+     * @dev Emitted when a relay is registered or re-registered. Looking at these events (and filtering out
      * {RelayRemoved} events) lets a client discover the list of available relays.
      */
     event RelayAdded(address indexed relay, address indexed owner, uint256 transactionFee, uint256 stake, uint256 unstakeDelay, string url);
@@ -105,7 +105,7 @@ interface IRelayHub {
     event Deposited(address indexed recipient, address indexed from, uint256 amount);
 
     /**
-     * @dev Returns an account's deposits. These can be either a contracts's funds, or a relay owner's revenue.
+     * @dev Returns an account's deposits. These can be either a contract's funds, or a relay owner's revenue.
      */
     function balanceOf(address target) external view returns (uint256);
 

+ 1 - 1
contracts/GSN/IRelayRecipient.sol

@@ -53,7 +53,7 @@ interface IRelayRecipient {
      *
      * Returns a value to be passed to {postRelayedCall}.
      *
-     * {preRelayedCall} is called with 100k gas: if it runs out during exection or otherwise reverts, the relayed call
+     * {preRelayedCall} is called with 100k gas: if it runs out during execution or otherwise reverts, the relayed call
      * will not be executed, but the recipient will still be charged for the transaction's cost.
      */
     function preRelayedCall(bytes calldata context) external returns (bytes32);

+ 1 - 1
contracts/math/SignedSafeMath.sol

@@ -9,7 +9,7 @@ pragma solidity ^0.7.0;
 library SignedSafeMath {
     int256 constant private _INT256_MIN = -2**255;
 
-        /**
+    /**
      * @dev Returns the multiplication of two signed integers, reverting on
      * overflow.
      *

+ 20 - 0
contracts/mocks/ClashingImplementation.sol

@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.6.0;
+
+
+/**
+ * @dev Implementation contract with an admin() function made to clash with
+ * @dev TransparentUpgradeableProxy's to test correct functioning of the
+ * @dev Transparent Proxy feature.
+ */
+contract ClashingImplementation {
+
+  function admin() external pure returns (address) {
+    return 0x0000000000000000000000000000000011111142;
+  }
+
+  function delegatedFunction() external pure returns (bool) {
+    return true;
+  }
+}

+ 57 - 0
contracts/mocks/DummyImplementation.sol

@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.6.0;
+
+abstract contract Impl {
+  function version() public pure virtual returns (string memory); 
+}
+
+contract DummyImplementation {
+  uint256 public value;
+  string public text;
+  uint256[] public values;
+
+  function initializeNonPayable() public {
+    value = 10;
+  }
+
+  function initializePayable() payable public {
+    value = 100;
+  }
+
+  function initializeNonPayable(uint256 _value) public {
+    value = _value;
+  }
+
+  function initializePayable(uint256 _value) payable public {
+    value = _value;
+  }
+
+  function initialize(uint256 _value, string memory _text, uint256[] memory _values) public {
+    value = _value;
+    text = _text;
+    values = _values;
+  }
+
+  function get() public pure returns (bool) {
+    return true;
+  }
+
+  function version() public pure virtual returns (string memory) {
+    return "V1";
+  }
+
+  function reverts() public pure {
+    require(false);
+  }
+}
+
+contract DummyImplementationV2 is DummyImplementation {
+  function migrate(uint256 newVal) payable public {
+    value = newVal;
+  }
+
+  function version() public pure override returns (string memory) {
+    return "V2";
+  }
+}

+ 36 - 0
contracts/mocks/InitializableMock.sol

@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.6.0;
+
+import "../proxy/Initializable.sol";
+
+/**
+ * @title InitializableMock
+ * @dev This contract is a mock to test initializable functionality
+ */
+contract InitializableMock is Initializable {
+
+  bool public initializerRan;
+  uint256 public x;
+
+  function initialize() public initializer {
+    initializerRan = true;
+  }
+
+  function initializeNested() public initializer {
+    initialize();
+  }
+
+  function initializeWithX(uint256 _x) public payable initializer {
+    x = _x;
+  }
+
+  function nonInitializable(uint256 _x) public payable {
+    x = _x;
+  }
+
+  function fail() public pure {
+    require(false, "InitializableMock forced failure");
+  }
+
+}

+ 76 - 0
contracts/mocks/MultipleInheritanceInitializableMocks.sol

@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.6.0;
+
+import "../proxy/Initializable.sol";
+
+// Sample contracts showing upgradeability with multiple inheritance.
+// Child contract inherits from Father and Mother contracts, and Father extends from Gramps.
+// 
+//         Human
+//       /       \
+//      |       Gramps
+//      |         |
+//    Mother    Father
+//      |         |
+//      -- Child --
+
+/**
+ * Sample base intializable contract that is a human
+ */
+contract SampleHuman is Initializable {
+  bool public isHuman;
+
+  function initialize() public initializer {
+    isHuman = true;
+  }
+}
+
+/**
+ * Sample base intializable contract that defines a field mother
+ */
+contract SampleMother is Initializable, SampleHuman {
+  uint256 public mother;
+
+  function initialize(uint256 value) public initializer virtual {
+    SampleHuman.initialize();
+    mother = value;
+  }
+}
+
+/**
+ * Sample base intializable contract that defines a field gramps
+ */
+contract SampleGramps is Initializable, SampleHuman {
+  string public gramps;
+
+  function initialize(string memory value) public initializer virtual {
+    SampleHuman.initialize();
+    gramps = value;
+  }
+}
+
+/**
+ * Sample base intializable contract that defines a field father and extends from gramps
+ */
+contract SampleFather is Initializable, SampleGramps {
+  uint256 public father;
+
+  function initialize(string memory _gramps, uint256 _father) public initializer {
+    SampleGramps.initialize(_gramps);
+    father = _father;
+  }
+}
+
+/**
+ * Child extends from mother, father (gramps)
+ */
+contract SampleChild is Initializable, SampleMother, SampleFather {
+  uint256 public child;
+
+  function initialize(uint256 _mother, string memory _gramps, uint256 _father, uint256 _child) public initializer {
+    SampleMother.initialize(_mother);
+    SampleFather.initialize(_gramps, _father);
+    child = _child;
+  }
+}

+ 66 - 0
contracts/mocks/RegressionImplementation.sol

@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.6.0;
+
+import "../proxy/Initializable.sol";
+
+contract Implementation1 is Initializable {
+  uint internal _value;
+
+  function initialize() public initializer {
+  }
+
+  function setValue(uint _number) public {
+    _value = _number;
+  }
+}
+
+contract Implementation2 is Initializable {
+  uint internal _value;
+
+  function initialize() public initializer {
+  }
+
+  function setValue(uint _number) public {
+    _value = _number;
+  }
+
+  function getValue() public view returns (uint) {
+    return _value;
+  }
+}
+
+contract Implementation3 is Initializable {
+  uint internal _value;
+
+  function initialize() public initializer {
+  }
+
+  function setValue(uint _number) public {
+    _value = _number;
+  }
+
+  function getValue(uint _number) public view returns (uint) {
+    return _value + _number;
+  }
+}
+
+contract Implementation4 is Initializable {
+  uint internal _value;
+
+  function initialize() public initializer {
+  }
+
+  function setValue(uint _number) public {
+    _value = _number;
+  }
+
+  function getValue() public view returns (uint) {
+    return _value;
+  }
+
+  // solhint-disable-next-line payable-fallback
+  fallback() external {
+    _value = 1;
+  }
+}

+ 49 - 0
contracts/mocks/SingleInheritanceInitializableMocks.sol

@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.6.0;
+
+import "../proxy/Initializable.sol";
+
+/**
+ * @title MigratableMockV1
+ * @dev This contract is a mock to test initializable functionality through migrations
+ */
+contract MigratableMockV1 is Initializable {
+  uint256 public x;
+
+  function initialize(uint256 value) public payable initializer {
+    x = value;
+  }
+}
+
+/**
+ * @title MigratableMockV2
+ * @dev This contract is a mock to test migratable functionality with params
+ */
+contract MigratableMockV2 is MigratableMockV1 {
+  bool internal _migratedV2;
+  uint256 public y;
+
+  function migrate(uint256 value, uint256 anotherValue) public payable {
+    require(!_migratedV2);
+    x = value;
+    y = anotherValue;
+    _migratedV2 = true;
+  }
+}
+
+/**
+ * @title MigratableMockV3
+ * @dev This contract is a mock to test migratable functionality without params
+ */
+contract MigratableMockV3 is MigratableMockV2 {
+  bool internal _migratedV3;
+
+  function migrate() public payable {
+    require(!_migratedV3);
+    uint256 oldX = x;
+    x = y;
+    y = oldX;
+    _migratedV3 = true;
+  }
+}

+ 1 - 1
contracts/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@openzeppelin/contracts",
-  "version": "3.1.0-solc-0.7",
+  "version": "3.2.0-rc.0",
   "description": "Secure Smart Contract library for Solidity",
   "files": [
     "**/*.sol",

+ 1 - 1
contracts/presets/ERC721PresetMinterPauserAutoId.sol

@@ -62,7 +62,7 @@ contract ERC721PresetMinterPauserAutoId is Context, AccessControl, ERC721Burnabl
     function mint(address to) public virtual {
         require(hasRole(MINTER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have minter role to mint");
 
-        // We can just use balanceOf to create the new tokenId because tokens
+        // We cannot just use balanceOf to create the new tokenId because tokens
         // can be burned (destroyed), so we need a separate counter.
         _mint(to, _tokenIdTracker.current());
         _tokenIdTracker.increment();

+ 62 - 0
contracts/proxy/Initializable.sol

@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity >=0.4.24 <0.7.0;
+
+
+/**
+ * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
+ * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
+ * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
+ * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
+ * 
+ * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
+ * possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}.
+ * 
+ * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
+ * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
+ */
+abstract contract Initializable {
+
+    /**
+     * @dev Indicates that the contract has been initialized.
+     */
+    bool private _initialized;
+
+    /**
+     * @dev Indicates that the contract is in the process of being initialized.
+     */
+    bool private _initializing;
+
+    /**
+     * @dev Modifier to protect an initializer function from being invoked twice.
+     */
+    modifier initializer() {
+        require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized");
+
+        bool isTopLevelCall = !_initializing;
+        if (isTopLevelCall) {
+            _initializing = true;
+            _initialized = true;
+        }
+
+        _;
+
+        if (isTopLevelCall) {
+            _initializing = false;
+        }
+    }
+
+    /// @dev Returns true if and only if the function is running in the constructor
+    function _isConstructor() private view returns (bool) {
+        // extcodesize checks the size of the code stored in an address, and
+        // address returns the current address. Since the code is still not
+        // deployed when running a constructor, any checks on its code size will
+        // yield zero, making it an effective way to detect if a contract is
+        // under construction or not.
+        address self = address(this);
+        uint256 cs;
+        // solhint-disable-next-line no-inline-assembly
+        assembly { cs := extcodesize(self) }
+        return cs == 0;
+    }
+}

+ 83 - 0
contracts/proxy/Proxy.sol

@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.6.0;
+
+/**
+ * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
+ * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
+ * be specified by overriding the virtual {_implementation} function.
+ * 
+ * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
+ * different contract through the {_delegate} function.
+ * 
+ * The success and return data of the delegated call will be returned back to the caller of the proxy.
+ */
+abstract contract Proxy {
+    /**
+     * @dev Delegates the current call to `implementation`.
+     * 
+     * This function does not return to its internall call site, it will return directly to the external caller.
+     */
+    function _delegate(address implementation) internal {
+        // solhint-disable-next-line no-inline-assembly
+        assembly {
+            // Copy msg.data. We take full control of memory in this inline assembly
+            // block because it will not return to Solidity code. We overwrite the
+            // Solidity scratch pad at memory position 0.
+            calldatacopy(0, 0, calldatasize())
+
+            // Call the implementation.
+            // out and outsize are 0 because we don't know the size yet.
+            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
+
+            // Copy the returned data.
+            returndatacopy(0, 0, returndatasize())
+
+            switch result
+            // delegatecall returns 0 on error.
+            case 0 { revert(0, returndatasize()) }
+            default { return(0, returndatasize()) }
+        }
+    }
+
+    /**
+     * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
+     * and {_fallback} should delegate.
+     */
+    function _implementation() internal virtual view returns (address);
+
+    /**
+     * @dev Delegates the current call to the address returned by `_implementation()`.
+     * 
+     * This function does not return to its internall call site, it will return directly to the external caller.
+     */
+    function _fallback() internal {
+        _beforeFallback();
+        _delegate(_implementation());
+    }
+
+    /**
+     * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
+     * function in the contract matches the call data.
+     */
+    fallback () payable external {
+        _fallback();
+    }
+
+    /**
+     * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
+     * is empty.
+     */
+    receive () payable external {
+        _fallback();
+    }
+
+    /**
+     * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
+     * call, or as part of the Solidity `fallback` or `receive` functions.
+     * 
+     * If overriden should call `super._beforeFallback()`.
+     */
+    function _beforeFallback() internal virtual {
+    }
+}

+ 77 - 0
contracts/proxy/ProxyAdmin.sol

@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.6.0;
+
+import "../access/Ownable.sol";
+import "./TransparentUpgradeableProxy.sol";
+
+/**
+ * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
+ * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
+ */
+contract ProxyAdmin is Ownable {
+
+    /**
+     * @dev Returns the current implementation of `proxy`.
+     * 
+     * Requirements:
+     * 
+     * - This contract must be the admin of `proxy`.
+     */
+    function getProxyImplementation(TransparentUpgradeableProxy proxy) public view returns (address) {
+        // We need to manually run the static call since the getter cannot be flagged as view
+        // bytes4(keccak256("implementation()")) == 0x5c60da1b
+        (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
+        require(success);
+        return abi.decode(returndata, (address));
+    }
+
+    /**
+     * @dev Returns the current admin of `proxy`.
+     * 
+     * Requirements:
+     * 
+     * - This contract must be the admin of `proxy`.
+     */
+    function getProxyAdmin(TransparentUpgradeableProxy proxy) public view returns (address) {
+        // We need to manually run the static call since the getter cannot be flagged as view
+        // bytes4(keccak256("admin()")) == 0xf851a440
+        (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
+        require(success);
+        return abi.decode(returndata, (address));
+    }
+
+    /**
+     * @dev Changes the admin of `proxy` to `newAdmin`.
+     * 
+     * Requirements:
+     * 
+     * - This contract must be the current admin of `proxy`.
+     */
+    function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public onlyOwner {
+        proxy.changeAdmin(newAdmin);
+    }
+
+    /**
+     * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
+     * 
+     * Requirements:
+     * 
+     * - This contract must be the admin of `proxy`.
+     */
+    function upgrade(TransparentUpgradeableProxy proxy, address implementation) public onlyOwner {
+        proxy.upgradeTo(implementation);
+    }
+
+    /**
+     * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
+     * {TransparentUpgradeableProxy-upgradeToAndCall}.
+     * 
+     * Requirements:
+     * 
+     * - This contract must be the admin of `proxy`.
+     */
+    function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable onlyOwner {
+        proxy.upgradeToAndCall{value: msg.value}(implementation, data);
+    }
+}

+ 26 - 0
contracts/proxy/README.adoc

@@ -0,0 +1,26 @@
+= Proxies
+
+[.readme-notice]
+NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/proxy
+
+This is a low-level set of contracts implementing the proxy pattern for upgradeability. For an in-depth overview of this pattern check out the xref:upgrades-plugins::proxies.adoc[Proxy Upgrade Pattern] page.
+
+The abstract {Proxy} contract implements the core delegation functionality. If the concrete proxies that we provide below are not suitable, we encourage building on top of this base contract since it contains an assembly block that may be hard to get right.
+
+Upgradeability is implemented in the {UpgradeableProxy} contract, although it provides only an internal upgrade interface. For an upgrade interface exposed externally to an admin, we provide {TransparentUpgradeableProxy}. Both of these contracts use the storage slots specified in https://eips.ethereum.org/EIPS/eip-1967[EIP1967] to avoid clashes with the storage of the implementation contract behind the proxy.
+
+CAUTION: Using upgradeable proxies correctly and securely is a difficult task that requires deep knowledge of the proxy pattern, Solidity, and the EVM. Unless you want a lot of low level control, we recommend using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins] for Truffle and Buidler.
+
+== Core
+
+{{Proxy}}
+
+{{UpgradeableProxy}}
+
+{{TransparentUpgradeableProxy}}
+
+== Utilities
+
+{{Initializable}}
+
+{{ProxyAdmin}}

+ 153 - 0
contracts/proxy/TransparentUpgradeableProxy.sol

@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.6.0;
+
+import "./UpgradeableProxy.sol";
+
+/**
+ * @dev This contract implements a proxy that is upgradeable by an admin.
+ * 
+ * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
+ * clashing], which can potentially be used in an attack, this contract uses the
+ * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
+ * things that go hand in hand:
+ * 
+ * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
+ * that call matches one of the admin functions exposed by the proxy itself.
+ * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
+ * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
+ * "admin cannot fallback to proxy target".
+ * 
+ * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
+ * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
+ * to sudden errors when trying to call a function from the proxy implementation.
+ * 
+ * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
+ * you should think of the `ProxyAdmin` instance as the real administrative inerface of your proxy.
+ */
+contract TransparentUpgradeableProxy is UpgradeableProxy {
+    /**
+     * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
+     * optionally initialized with `_data` as explained in {UpgradeableProxy-constructor}.
+     */
+    constructor(address _logic, address _admin, bytes memory _data) public payable UpgradeableProxy(_logic, _data) {
+        assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
+        _setAdmin(_admin);
+    }
+
+    /**
+     * @dev Emitted when the admin account has changed.
+     */
+    event AdminChanged(address previousAdmin, address newAdmin);
+
+    /**
+     * @dev Storage slot with the admin of the contract.
+     * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
+     * validated in the constructor.
+     */
+    bytes32 private constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
+
+    /**
+     * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
+     */
+    modifier ifAdmin() {
+        if (msg.sender == _admin()) {
+            _;
+        } else {
+            _fallback();
+        }
+    }
+
+    /**
+     * @dev Returns the current admin.
+     * 
+     * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
+     * 
+     * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
+     * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
+     * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
+     */
+    function admin() external ifAdmin returns (address) {
+        return _admin();
+    }
+
+    /**
+     * @dev Returns the current implementation.
+     * 
+     * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
+     * 
+     * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
+     * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
+     * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
+     */
+    function implementation() external ifAdmin returns (address) {
+        return _implementation();
+    }
+
+    /**
+     * @dev Changes the admin of the proxy.
+     * 
+     * Emits an {AdminChanged} event.
+     * 
+     * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
+     */
+    function changeAdmin(address newAdmin) external ifAdmin {
+        require(newAdmin != address(0), "TransparentUpgradeableProxy: new admin is the zero address");
+        emit AdminChanged(_admin(), newAdmin);
+        _setAdmin(newAdmin);
+    }
+
+    /**
+     * @dev Upgrade the implementation of the proxy.
+     * 
+     * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
+     */
+    function upgradeTo(address newImplementation) external ifAdmin {
+        _upgradeTo(newImplementation);
+    }
+
+    /**
+     * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
+     * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
+     * proxied contract.
+     * 
+     * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
+     */
+    function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
+        _upgradeTo(newImplementation);
+        // solhint-disable-next-line avoid-low-level-calls
+        (bool success,) = newImplementation.delegatecall(data);
+        require(success);
+    }
+
+    /**
+     * @dev Returns the current admin.
+     */
+    function _admin() internal view returns (address adm) {
+        bytes32 slot = _ADMIN_SLOT;
+        // solhint-disable-next-line no-inline-assembly
+        assembly {
+            adm := sload(slot)
+        }
+    }
+
+    /**
+     * @dev Stores a new address in the EIP1967 admin slot.
+     */
+    function _setAdmin(address newAdmin) private {
+        bytes32 slot = _ADMIN_SLOT;
+
+        // solhint-disable-next-line no-inline-assembly
+        assembly {
+            sstore(slot, newAdmin)
+        }
+    }
+
+    /**
+     * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
+     */
+    function _beforeFallback() internal override virtual {
+        require(msg.sender != _admin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
+        super._beforeFallback();
+    }
+}

+ 80 - 0
contracts/proxy/UpgradeableProxy.sol

@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.6.0;
+
+import "./Proxy.sol";
+import "../utils/Address.sol";
+
+/**
+ * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
+ * implementation address that can be changed. This address is stored in storage in the location specified by
+ * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
+ * implementation behind the proxy.
+ * 
+ * Upgradeability is only provided internally through {_upgradeTo}. For an externally upgradeable proxy see
+ * {TransparentUpgradeableProxy}.
+ */
+contract UpgradeableProxy is Proxy {
+    /**
+     * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
+     * 
+     * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
+     * function call, and allows initializating the storage of the proxy like a Solidity constructor.
+     */
+    constructor(address _logic, bytes memory _data) public payable {
+        assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
+        _setImplementation(_logic);
+        if(_data.length > 0) {
+            // solhint-disable-next-line avoid-low-level-calls
+            (bool success,) = _logic.delegatecall(_data);
+            require(success);
+        }
+    }
+
+    /**
+     * @dev Emitted when the implementation is upgraded.
+     */
+    event Upgraded(address indexed implementation);
+
+    /**
+     * @dev Storage slot with the address of the current implementation.
+     * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
+     * validated in the constructor.
+     */
+    bytes32 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
+
+    /**
+     * @dev Returns the current implementation address.
+     */
+    function _implementation() internal override view returns (address impl) {
+        bytes32 slot = _IMPLEMENTATION_SLOT;
+        // solhint-disable-next-line no-inline-assembly
+        assembly {
+            impl := sload(slot)
+        }
+    }
+
+    /**
+     * @dev Upgrades the proxy to a new implementation.
+     * 
+     * Emits an {Upgraded} event.
+     */
+    function _upgradeTo(address newImplementation) internal {
+        _setImplementation(newImplementation);
+        emit Upgraded(newImplementation);
+    }
+
+    /**
+     * @dev Stores a new address in the EIP1967 implementation slot.
+     */
+    function _setImplementation(address newImplementation) private {
+        require(Address.isContract(newImplementation), "UpgradeableProxy: new implementation is not a contract");
+
+        bytes32 slot = _IMPLEMENTATION_SLOT;
+
+        // solhint-disable-next-line no-inline-assembly
+        assembly {
+            sstore(slot, newImplementation)
+        }
+    }
+}

+ 4 - 4
contracts/token/ERC1155/ERC1155.sol

@@ -28,7 +28,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
     // Mapping from account to operator approvals
     mapping (address => mapping(address => bool)) private _operatorApprovals;
 
-    // Used as the URI for all token types by relying on ID substition, e.g. https://token-cdn-domain/{id}.json
+    // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
     string private _uri;
 
     /*
@@ -66,7 +66,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
      * @dev See {IERC1155MetadataURI-uri}.
      *
      * This implementation returns the same URI for *all* token types. It relies
-     * on the token type ID substituion mechanism
+     * on the token type ID substitution mechanism
      * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
      *
      * Clients calling this function must replace the `\{id\}` substring with the
@@ -208,10 +208,10 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
 
     /**
      * @dev Sets a new URI for all token types, by relying on the token type ID
-     * substituion mechanism
+     * substitution mechanism
      * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
      *
-     * By this mechanism, any occurence of the `\{id\}` substring in either the
+     * By this mechanism, any occurrence of the `\{id\}` substring in either the
      * URI or any of the amounts in the JSON file at said URI will be replaced by
      * clients with the token type ID.
      *

+ 1 - 1
contracts/token/ERC1155/IERC1155.sol

@@ -12,7 +12,7 @@ import "../../introspection/IERC165.sol";
  */
 interface IERC1155 is IERC165 {
     /**
-     * @dev Emitted when `value` tokens of token type `id` are transfered from `from` to `to` by `operator`.
+     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
      */
     event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
 

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

@@ -258,9 +258,9 @@ contract ERC20 is Context, IERC20 {
     }
 
     /**
-     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
+     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
      *
-     * This is internal function is equivalent to `approve`, and can be used to
+     * This internal function is equivalent to `approve`, and can be used to
      * e.g. set automatic allowances for certain subsystems, etc.
      *
      * Emits an {Approval} event.

+ 15 - 18
contracts/token/ERC20/ERC20Snapshot.sol

@@ -104,28 +104,25 @@ abstract contract ERC20Snapshot is ERC20 {
         return snapshotted ? value : totalSupply();
     }
 
-    // _transfer, _mint and _burn are the only functions where the balances are modified, so it is there that the
-    // snapshots are updated. Note that the update happens _before_ the balance change, with the pre-modified value.
-    // The same is true for the total supply and _mint and _burn.
-    function _transfer(address from, address to, uint256 value) internal virtual override {
-        _updateAccountSnapshot(from);
-        _updateAccountSnapshot(to);
 
-        super._transfer(from, to, value);
-    }
+    // Update balance and/or total supply snapshots before the values are modified. This is implemented
+    // in the _beforeTokenTransfer hook, which is executed for _mint, _burn, and _transfer operations.
+    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
+      super._beforeTokenTransfer(from, to, amount);
 
-    function _mint(address account, uint256 value) internal virtual override {
-        _updateAccountSnapshot(account);
+      if (from == address(0)) {
+        // mint
+        _updateAccountSnapshot(to);
         _updateTotalSupplySnapshot();
-
-        super._mint(account, value);
-    }
-
-    function _burn(address account, uint256 value) internal virtual override {
-        _updateAccountSnapshot(account);
+      } else if (to == address(0)) {
+        // burn
+        _updateAccountSnapshot(from);
         _updateTotalSupplySnapshot();
-
-        super._burn(account, value);
+      } else {
+        // transfer
+        _updateAccountSnapshot(from);
+        _updateAccountSnapshot(to);
+      }
     }
 
     function _valueAt(uint256 snapshotId, Snapshots storage snapshots)

+ 1 - 1
contracts/token/ERC20/README.adoc

@@ -5,7 +5,7 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/
 
 This set of interfaces, contracts, and utilities are all related to the https://eips.ethereum.org/EIPS/eip-20[ERC20 Token Standard].
 
-TIP: For an overview of ERC20 tokens and a walkthrough on how to create a token contract read our xref:ROOT:erc20.adoc[ERC20 guide].
+TIP: For an overview of ERC20 tokens and a walk through on how to create a token contract read our xref:ROOT:erc20.adoc[ERC20 guide].
 
 There a few core contracts that implement the behavior specified in the EIP:
 

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

@@ -254,7 +254,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable
      * `_data` is additional data, it has no specified format and it is sent in call to `to`.
      *
      * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
-     * implement alternative mecanisms to perform token transfer, such as signature-based.
+     * implement alternative mechanisms to perform token transfer, such as signature-based.
      *
      * Requirements:
      *

+ 1 - 1
contracts/token/ERC721/IERC721.sol

@@ -9,7 +9,7 @@ import "../../introspection/IERC165.sol";
  */
 interface IERC721 is IERC165 {
     /**
-     * @dev Emitted when `tokenId` token is transfered from `from` to `to`.
+     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
      */
     event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
 

+ 1 - 1
contracts/token/ERC721/README.adoc

@@ -5,7 +5,7 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/
 
 This set of interfaces, contracts, and utilities are all related to the https://eips.ethereum.org/EIPS/eip-721[ERC721 Non-Fungible Token Standard].
 
-TIP: For a walkthrough on how to create an ERC721 token read our xref:ROOT:erc721.adoc[ERC721 guide].
+TIP: For a walk through on how to create an ERC721 token read our xref:ROOT:erc721.adoc[ERC721 guide].
 
 The EIP consists of three interfaces, found here as {IERC721}, {IERC721Metadata}, and {IERC721Enumerable}. Only the first one is required in a contract to be ERC721 compliant. However, all three are implemented in {ERC721}.
 

+ 1 - 1
contracts/token/ERC777/README.adoc

@@ -5,7 +5,7 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/
 
 This set of interfaces and contracts are all related to the [ERC777 token standard](https://eips.ethereum.org/EIPS/eip-777).
 
-TIP: For an overview of ERC777 tokens and a walkthrough on how to create a token contract read our xref:ROOT:erc777.adoc[ERC777 guide].
+TIP: For an overview of ERC777 tokens and a walk through on how to create a token contract read our xref:ROOT:erc777.adoc[ERC777 guide].
 
 The token behavior itself is implemented in the core contracts: {IERC777}, {ERC777}.
 

+ 1 - 1
contracts/utils/README.adoc

@@ -7,7 +7,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
 
 Security tools include:
 
- * {Pausable}: provides a simple way to halt activity in your contracts (often in reponse to an external threat).
+ * {Pausable}: provides a simple way to halt activity in your contracts (often in response to an external threat).
  * {ReentrancyGuard}: protects you from https://blog.openzeppelin.com/reentrancy-after-istanbul/[reentrant calls].
 
 The {Address}, {Arrays} and {Strings} libraries provide more operations related to these native data types, while {SafeCast} adds ways to safely convert between the different signed and unsigned numeric types.

+ 1 - 1
docs/modules/ROOT/pages/access-control.adoc

@@ -131,7 +131,7 @@ By default, **accounts with a role cannot grant it or revoke it from other accou
 
 Every role has an associated admin role, which grants permission to call the `grantRole` and `revokeRole` functions. A role can be granted or revoked by using these if the calling account has the corresponding admin role. Multiple roles may have the same admin role to make management easier. A role's admin can even be the same role itself, which would cause accounts with that role to be able to also grant and revoke it.
 
-This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also provides an easy way to manage simpler applications. `AccessControl` includes a special role, called `DEFAULT_ADMIN_ROLE`, which acts as the **default admin role for all roles**. An account with this role will be able to manage any other role, unless `\_setRoleAdmin` is used to select a new admin role.
+This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also provides an easy way to manage simpler applications. `AccessControl` includes a special role, called `DEFAULT_ADMIN_ROLE`, which acts as the **default admin role for all roles**. An account with this role will be able to manage any other role, unless `_setRoleAdmin` is used to select a new admin role.
 
 Let's take a look at the ERC20 token example, this time taking advantage of the default admin role:
 

+ 2 - 2
docs/modules/ROOT/pages/erc1155.adoc

@@ -94,9 +94,9 @@ The metadata uri can be obtained:
 "https://game.example/api/item/{id}.json"
 ----
 
-The `uri` can include the string `{id}` which clients must replace with the actual token ID, in lowercase hexadecimal (with no 0x prefix) and leading zero padded to 64 hex characters.
+The `uri` can include the string `++{id}++` which clients must replace with the actual token ID, in lowercase hexadecimal (with no 0x prefix) and leading zero padded to 64 hex characters.
 
-For token ID `2` and uri `https://game.example/api/item/{id}.json` clients would replace `{id}` with `0000000000000000000000000000000000000000000000000000000000000002` to retrieve JSON at `https://game.example/api/item/0000000000000000000000000000000000000000000000000000000000000002.json`.
+For token ID `2` and uri `++https://game.example/api/item/{id}.json++` clients would replace `++{id}++` with `0000000000000000000000000000000000000000000000000000000000000002` to retrieve JSON at `https://game.example/api/item/0000000000000000000000000000000000000000000000000000000000000002.json`.
 
 The JSON document for token ID 2 might look something like:
 

+ 1 - 1
docs/modules/ROOT/pages/index.adoc

@@ -52,7 +52,7 @@ The guides in the sidebar will teach about different concepts, and how to use th
 * xref:gsn.adoc[Gas Station Network]: let your users interact with your contracts without having to pay for gas themselves.
 * xref:utilities.adoc[Utilities]: generic useful tools, including non-overflowing math, signature verification, and trustless paying systems.
 
-The xref:api:token/ERC20.adoc[full API] is also thoroughly documented, and serves as a great reference when developing your smart contract application. You can also ask for help or follow Contracts's development in the https://forum.openzeppelin.com[community forum].
+The xref:api:token/ERC20.adoc[full API] is also thoroughly documented, and serves as a great reference when developing your smart contract application. You can also ask for help or follow Contracts' development in the https://forum.openzeppelin.com[community forum].
 
 Finally, you may want to take a look at the https://blog.openzeppelin.com/guides/[guides on our blog], which cover several common use cases and good practices.. The following articles provide great background reading, though please note, some of the referenced tools have changed as the tooling in the ecosystem continues to rapidly evolve.
 

+ 2 - 2
docs/modules/ROOT/pages/releases-stability.adoc

@@ -69,12 +69,12 @@ While attempts will generally be made to lower the gas costs of working with Ope
 [[bugfixes]]
 === Bug Fixes
 
-The API stability guarantees may need to be broken in order to fix a bug, and we will do so. This decision won't be made lightly however, and all options will be explored to make the change as non-disruptive as possible. When sufficient, contracts or functions which may result in unsafe behaviour will be deprecated instead of removed (e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1543[#1543] and https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1550[#1550]).
+The API stability guarantees may need to be broken in order to fix a bug, and we will do so. This decision won't be made lightly however, and all options will be explored to make the change as non-disruptive as possible. When sufficient, contracts or functions which may result in unsafe behavior will be deprecated instead of removed (e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1543[#1543] and https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1550[#1550]).
 
 [[solidity-compiler-version]]
 === Solidity Compiler Version
 
-Starting on version 0.5.0, the Solidity team switched to a faster release cycle, with minor releases every few weeks (v0.5.0 was released on November 2018, and v0.5.5 on March 2019), and major, breaking-change releases every couple months (with v0.6.0 scheduled for late March 2019). Including the compiler version in OpenZeppelin Contract's stability guarantees would therefore force the library to either stick to old compilers, or release frequent major updates simply to keep up with newer Solidity releases.
+Starting on version 0.5.0, the Solidity team switched to a faster release cycle, with minor releases every few weeks (v0.5.0 was released on November 2018, and v0.5.5 on March 2019), and major, breaking-change releases every couple of months (with v0.6.0 released on December 2019 and v0.7.0 on July 2020). Including the compiler version in OpenZeppelin Contract's stability guarantees would therefore force the library to either stick to old compilers, or release frequent major updates simply to keep up with newer Solidity releases.
 
 Because of this, *the minimum required Solidity compiler version is not part of the stability guarantees*, and users may be required to upgrade their compiler when using newer versions of Contracts. Bug fixes will still be backported to older library releases so that all versions currently in use receive these updates.
 


Разница между файлами не показана из-за своего большого размера
+ 5 - 0
logo.svg


+ 1 - 1
package-lock.json

@@ -1,6 +1,6 @@
 {
   "name": "openzeppelin-solidity",
-  "version": "3.1.0-solc-0.7",
+  "version": "3.2.0-rc.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "openzeppelin-solidity",
-  "version": "3.1.0-solc-0.7",
+  "version": "3.2.0-rc.0",
   "description": "Secure Smart Contract library for Solidity",
   "files": [
     "/contracts/**/*.sol",

+ 34 - 30
test/access/Ownable.test.js

@@ -13,42 +13,46 @@ describe('Ownable', function () {
     this.ownable = await Ownable.new({ from: owner });
   });
 
-  it('should have an owner', async function () {
+  it('has an owner', async function () {
     expect(await this.ownable.owner()).to.equal(owner);
   });
 
-  it('changes owner after transfer', async function () {
-    const receipt = await this.ownable.transferOwnership(other, { from: owner });
-    expectEvent(receipt, 'OwnershipTransferred');
-
-    expect(await this.ownable.owner()).to.equal(other);
-  });
-
-  it('should prevent non-owners from transferring', async function () {
-    await expectRevert(
-      this.ownable.transferOwnership(other, { from: other }),
-      'Ownable: caller is not the owner'
-    );
+  describe('transfer ownership', function () {
+    it('changes owner after transfer', async function () {
+      const receipt = await this.ownable.transferOwnership(other, { from: owner });
+      expectEvent(receipt, 'OwnershipTransferred');
+
+      expect(await this.ownable.owner()).to.equal(other);
+    });
+
+    it('prevents non-owners from transferring', async function () {
+      await expectRevert(
+        this.ownable.transferOwnership(other, { from: other }),
+        'Ownable: caller is not the owner'
+      );
+    });
+
+    it('guards ownership against stuck state', async function () {
+      await expectRevert(
+        this.ownable.transferOwnership(ZERO_ADDRESS, { from: owner }),
+        'Ownable: new owner is the zero address'
+      );
+    });
   });
 
-  it('should guard ownership against stuck state', async function () {
-    await expectRevert(
-      this.ownable.transferOwnership(ZERO_ADDRESS, { from: owner }),
-      'Ownable: new owner is the zero address'
-    );
-  });
-
-  it('loses owner after renouncement', async function () {
-    const receipt = await this.ownable.renounceOwnership({ from: owner });
-    expectEvent(receipt, 'OwnershipTransferred');
+  describe('renounce ownership', function () {
+    it('loses owner after renouncement', async function () {
+      const receipt = await this.ownable.renounceOwnership({ from: owner });
+      expectEvent(receipt, 'OwnershipTransferred');
 
-    expect(await this.ownable.owner()).to.equal(ZERO_ADDRESS);
-  });
+      expect(await this.ownable.owner()).to.equal(ZERO_ADDRESS);
+    });
 
-  it('should prevent non-owners from renouncement', async function () {
-    await expectRevert(
-      this.ownable.renounceOwnership({ from: other }),
-      'Ownable: caller is not the owner'
-    );
+    it('prevents non-owners from renouncement', async function () {
+      await expectRevert(
+        this.ownable.renounceOwnership({ from: other }),
+        'Ownable: caller is not the owner'
+      );
+    });
   });
 });

+ 3 - 3
test/cryptography/MerkleProof.test.js

@@ -15,7 +15,7 @@ describe('MerkleProof', function () {
   });
 
   describe('verify', function () {
-    it('should return true for a valid Merkle proof', async function () {
+    it('returns true for a valid Merkle proof', async function () {
       const elements = ['a', 'b', 'c', 'd'];
       const merkleTree = new MerkleTree(elements);
 
@@ -28,7 +28,7 @@ describe('MerkleProof', function () {
       expect(await this.merkleProof.verify(proof, root, leaf)).to.equal(true);
     });
 
-    it('should return false for an invalid Merkle proof', async function () {
+    it('returns false for an invalid Merkle proof', async function () {
       const correctElements = ['a', 'b', 'c'];
       const correctMerkleTree = new MerkleTree(correctElements);
 
@@ -44,7 +44,7 @@ describe('MerkleProof', function () {
       expect(await this.merkleProof.verify(badProof, correctRoot, correctLeaf)).to.equal(false);
     });
 
-    it('should return false for a Merkle proof of invalid length', async function () {
+    it('returns false for a Merkle proof of invalid length', async function () {
       const elements = ['a', 'b', 'c'];
       const merkleTree = new MerkleTree(elements);
 

+ 1 - 1
test/helpers/sign.js

@@ -38,7 +38,7 @@ const getSignFor = (contract, signer) => (redeemer, methodName, methodArgs = [])
     redeemer,
   ];
 
-  const REAL_SIGNATURE_SIZE = 2 * 65; // 65 bytes in hexadecimal string legnth
+  const REAL_SIGNATURE_SIZE = 2 * 65; // 65 bytes in hexadecimal string length
   const PADDED_SIGNATURE_SIZE = 2 * 96; // 96 bytes in hexadecimal string length
   const DUMMY_SIGNATURE = `0x${web3.utils.padLeft('', REAL_SIGNATURE_SIZE)}`;
 

+ 3 - 3
test/introspection/SupportsInterface.behavior.js

@@ -57,11 +57,11 @@ function shouldSupportInterfaces (interfaces = []) {
       const interfaceId = INTERFACE_IDS[k];
       describe(k, function () {
         describe('ERC165\'s supportsInterface(bytes4)', function () {
-          it('should use less than 30k gas', async function () {
+          it('uses less than 30k gas', async function () {
             expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.be.lte(30000);
           });
 
-          it('should claim support', async function () {
+          it('claims support', async function () {
             expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true);
           });
         });
@@ -69,7 +69,7 @@ function shouldSupportInterfaces (interfaces = []) {
         for (const fnName of INTERFACES[k]) {
           const fnSig = FN_SIGNATURES[fnName];
           describe(fnName, function () {
-            it('should be implemented', function () {
+            it('has to be implemented', function () {
               expect(this.contractUnderTest.abi.filter(fn => fn.signature === fnSig).length).to.equal(1);
             });
           });

+ 23 - 20
test/payment/PaymentSplitter.test.js

@@ -54,45 +54,48 @@ describe('PaymentSplitter', function () {
       this.contract = await PaymentSplitter.new(this.payees, this.shares);
     });
 
-    it('should have total shares', async function () {
+    it('has total shares', async function () {
       expect(await this.contract.totalShares()).to.be.bignumber.equal('100');
     });
 
-    it('should have payees', async function () {
+    it('has payees', async function () {
       await Promise.all(this.payees.map(async (payee, index) => {
         expect(await this.contract.payee(index)).to.equal(payee);
         expect(await this.contract.released(payee)).to.be.bignumber.equal('0');
       }));
     });
 
-    it('should accept payments', async function () {
+    it('accepts payments', async function () {
       await send.ether(owner, this.contract.address, amount);
 
       expect(await balance.current(this.contract.address)).to.be.bignumber.equal(amount);
     });
 
-    it('should store shares if address is payee', async function () {
-      expect(await this.contract.shares(payee1)).to.be.bignumber.not.equal('0');
-    });
-
-    it('should not store shares if address is not payee', async function () {
-      expect(await this.contract.shares(nonpayee1)).to.be.bignumber.equal('0');
-    });
+    describe('shares', async function () {
+      it('stores shares if address is payee', async function () {
+        expect(await this.contract.shares(payee1)).to.be.bignumber.not.equal('0');
+      });
 
-    it('should throw if no funds to claim', async function () {
-      await expectRevert(this.contract.release(payee1),
-        'PaymentSplitter: account is not due payment'
-      );
+      it('does not store shares if address is not payee', async function () {
+        expect(await this.contract.shares(nonpayee1)).to.be.bignumber.equal('0');
+      });
     });
 
-    it('should throw if non-payee want to claim', async function () {
-      await send.ether(payer1, this.contract.address, amount);
-      await expectRevert(this.contract.release(nonpayee1),
-        'PaymentSplitter: account has no shares'
-      );
+    describe('release', async function () {
+      it('reverts if no funds to claim', async function () {
+        await expectRevert(this.contract.release(payee1),
+          'PaymentSplitter: account is not due payment'
+        );
+      });
+      it('reverts if non-payee want to claim', async function () {
+        await send.ether(payer1, this.contract.address, amount);
+        await expectRevert(this.contract.release(nonpayee1),
+          'PaymentSplitter: account has no shares'
+        );
+      });
     });
 
-    it('should distribute funds to payees', async function () {
+    it('distributes funds to payees', async function () {
       await send.ether(payer1, this.contract.address, amount);
 
       // receive funds

+ 29 - 25
test/payment/PullPayment.test.js

@@ -15,35 +15,39 @@ describe('PullPayment', function () {
     this.contract = await PullPaymentMock.new({ value: amount });
   });
 
-  it('can record an async payment correctly', async function () {
-    await this.contract.callTransfer(payee1, 100, { from: payer });
-    expect(await this.contract.payments(payee1)).to.be.bignumber.equal('100');
+  describe('payments', function () {
+    it('can record an async payment correctly', async function () {
+      await this.contract.callTransfer(payee1, 100, { from: payer });
+      expect(await this.contract.payments(payee1)).to.be.bignumber.equal('100');
+    });
+
+    it('can add multiple balances on one account', async function () {
+      await this.contract.callTransfer(payee1, 200, { from: payer });
+      await this.contract.callTransfer(payee1, 300, { from: payer });
+      expect(await this.contract.payments(payee1)).to.be.bignumber.equal('500');
+    });
+
+    it('can add balances on multiple accounts', async function () {
+      await this.contract.callTransfer(payee1, 200, { from: payer });
+      await this.contract.callTransfer(payee2, 300, { from: payer });
+
+      expect(await this.contract.payments(payee1)).to.be.bignumber.equal('200');
+
+      expect(await this.contract.payments(payee2)).to.be.bignumber.equal('300');
+    });
   });
 
-  it('can add multiple balances on one account', async function () {
-    await this.contract.callTransfer(payee1, 200, { from: payer });
-    await this.contract.callTransfer(payee1, 300, { from: payer });
-    expect(await this.contract.payments(payee1)).to.be.bignumber.equal('500');
-  });
-
-  it('can add balances on multiple accounts', async function () {
-    await this.contract.callTransfer(payee1, 200, { from: payer });
-    await this.contract.callTransfer(payee2, 300, { from: payer });
-
-    expect(await this.contract.payments(payee1)).to.be.bignumber.equal('200');
-
-    expect(await this.contract.payments(payee2)).to.be.bignumber.equal('300');
-  });
-
-  it('can withdraw payment', async function () {
-    const balanceTracker = await balance.tracker(payee1);
+  describe('withdrawPayments', function () {
+    it('can withdraw payment', async function () {
+      const balanceTracker = await balance.tracker(payee1);
 
-    await this.contract.callTransfer(payee1, amount, { from: payer });
-    expect(await this.contract.payments(payee1)).to.be.bignumber.equal(amount);
+      await this.contract.callTransfer(payee1, amount, { from: payer });
+      expect(await this.contract.payments(payee1)).to.be.bignumber.equal(amount);
 
-    await this.contract.withdrawPayments(payee1);
+      await this.contract.withdrawPayments(payee1);
 
-    expect(await balanceTracker.delta()).to.be.bignumber.equal(amount);
-    expect(await this.contract.payments(payee1)).to.be.bignumber.equal('0');
+      expect(await balanceTracker.delta()).to.be.bignumber.equal(amount);
+      expect(await this.contract.payments(payee1)).to.be.bignumber.equal('0');
+    });
   });
 });

+ 81 - 0
test/proxy/Initializable.test.js

@@ -0,0 +1,81 @@
+const { contract } = require('@openzeppelin/test-environment');
+
+const { expectRevert } = require('@openzeppelin/test-helpers');
+
+const { assert } = require('chai');
+
+const InitializableMock = contract.fromArtifact('InitializableMock');
+const SampleChild = contract.fromArtifact('SampleChild');
+
+describe('Initializable', function () {
+  describe('basic testing without inheritance', function () {
+    beforeEach('deploying', async function () {
+      this.contract = await InitializableMock.new();
+    });
+
+    context('before initialize', function () {
+      it('initializer has not run', async function () {
+        assert.isFalse(await this.contract.initializerRan());
+      });
+    });
+
+    context('after initialize', function () {
+      beforeEach('initializing', async function () {
+        await this.contract.initialize();
+      });
+
+      it('initializer has run', async function () {
+        assert.isTrue(await this.contract.initializerRan());
+      });
+
+      it('initializer does not run again', async function () {
+        await expectRevert(this.contract.initialize(), 'Initializable: contract is already initialized');
+      });
+    });
+
+    context('after nested initialize', function () {
+      beforeEach('initializing', async function () {
+        await this.contract.initializeNested();
+      });
+
+      it('initializer has run', async function () {
+        assert.isTrue(await this.contract.initializerRan());
+      });
+    });
+  });
+
+  describe('complex testing with inheritance', function () {
+    const mother = 12;
+    const gramps = '56';
+    const father = 34;
+    const child = 78;
+
+    beforeEach('deploying', async function () {
+      this.contract = await SampleChild.new();
+    });
+
+    beforeEach('initializing', async function () {
+      await this.contract.initialize(mother, gramps, father, child);
+    });
+
+    it('initializes human', async function () {
+      assert.equal(await this.contract.isHuman(), true);
+    });
+
+    it('initializes mother', async function () {
+      assert.equal(await this.contract.mother(), mother);
+    });
+
+    it('initializes gramps', async function () {
+      assert.equal(await this.contract.gramps(), gramps);
+    });
+
+    it('initializes father', async function () {
+      assert.equal(await this.contract.father(), father);
+    });
+
+    it('initializes child', async function () {
+      assert.equal(await this.contract.child(), child);
+    });
+  });
+});

+ 119 - 0
test/proxy/ProxyAdmin.test.js

@@ -0,0 +1,119 @@
+const { accounts, contract } = require('@openzeppelin/test-environment');
+
+const { expectRevert } = require('@openzeppelin/test-helpers');
+
+const { expect } = require('chai');
+
+const ImplV1 = contract.fromArtifact('DummyImplementation');
+const ImplV2 = contract.fromArtifact('DummyImplementationV2');
+const ProxyAdmin = contract.fromArtifact('ProxyAdmin');
+const TransparentUpgradeableProxy = contract.fromArtifact('TransparentUpgradeableProxy');
+
+describe('ProxyAdmin', function () {
+  const [proxyAdminOwner, newAdmin, anotherAccount] = accounts;
+
+  before('set implementations', async function () {
+    this.implementationV1 = await ImplV1.new();
+    this.implementationV2 = await ImplV2.new();
+  });
+
+  beforeEach(async function () {
+    const initializeData = Buffer.from('');
+    this.proxyAdmin = await ProxyAdmin.new({ from: proxyAdminOwner });
+    this.proxy = await TransparentUpgradeableProxy.new(
+      this.implementationV1.address,
+      this.proxyAdmin.address,
+      initializeData,
+      { from: proxyAdminOwner },
+    );
+  });
+
+  it('has an owner', async function () {
+    expect(await this.proxyAdmin.owner()).to.equal(proxyAdminOwner);
+  });
+
+  describe('#getProxyAdmin', function () {
+    it('returns proxyAdmin as admin of the proxy', async function () {
+      const admin = await this.proxyAdmin.getProxyAdmin(this.proxy.address);
+      expect(admin).to.be.equal(this.proxyAdmin.address);
+    });
+  });
+
+  describe('#changeProxyAdmin', function () {
+    it('fails to change proxy admin if its not the proxy owner', async function () {
+      await expectRevert(
+        this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: anotherAccount }),
+        'caller is not the owner',
+      );
+    });
+
+    it('changes proxy admin', async function () {
+      await this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: proxyAdminOwner });
+      expect(await this.proxy.admin.call({ from: newAdmin })).to.eq(newAdmin);
+    });
+  });
+
+  describe('#getProxyImplementation', function () {
+    it('returns proxy implementation address', async function () {
+      const implementationAddress = await this.proxyAdmin.getProxyImplementation(this.proxy.address);
+      expect(implementationAddress).to.be.equal(this.implementationV1.address);
+    });
+  });
+
+  describe('#upgrade', function () {
+    context('with unauthorized account', function () {
+      it('fails to upgrade', async function () {
+        await expectRevert(
+          this.proxyAdmin.upgrade(this.proxy.address, this.implementationV2.address, { from: anotherAccount }),
+          'caller is not the owner',
+        );
+      });
+    });
+
+    context('with authorized account', function () {
+      it('upgrades implementation', async function () {
+        await this.proxyAdmin.upgrade(this.proxy.address, this.implementationV2.address, { from: proxyAdminOwner });
+        const implementationAddress = await this.proxyAdmin.getProxyImplementation(this.proxy.address);
+        expect(implementationAddress).to.be.equal(this.implementationV2.address);
+      });
+    });
+  });
+
+  describe('#upgradeAndCall', function () {
+    context('with unauthorized account', function () {
+      it('fails to upgrade', async function () {
+        const callData = new ImplV1('').contract.methods['initializeNonPayable(uint256)'](1337).encodeABI();
+        await expectRevert(
+          this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData,
+            { from: anotherAccount }
+          ),
+          'caller is not the owner',
+        );
+      });
+    });
+
+    context('with authorized account', function () {
+      context('with invalid callData', function () {
+        it('fails to upgrade', async function () {
+          const callData = '0x12345678';
+          await expectRevert.unspecified(
+            this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData,
+              { from: proxyAdminOwner }
+            ),
+          );
+        });
+      });
+
+      context('with valid callData', function () {
+        it('upgrades implementation', async function () {
+          const callData = new ImplV1('').contract.methods['initializeNonPayable(uint256)'](1337).encodeABI();
+          await this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData,
+            { from: proxyAdminOwner }
+          );
+          const implementationAddress = await this.proxyAdmin.getProxyImplementation(this.proxy.address);
+          expect(implementationAddress).to.be.equal(this.implementationV2.address);
+        });
+      });
+    });
+  });
+});

+ 436 - 0
test/proxy/TransparentUpgradeableProxy.behaviour.js

@@ -0,0 +1,436 @@
+const { contract, web3 } = require('@openzeppelin/test-environment');
+
+const { BN, expectRevert, expectEvent, constants } = require('@openzeppelin/test-helpers');
+const { ZERO_ADDRESS } = constants;
+const { toChecksumAddress, keccak256 } = require('ethereumjs-util');
+
+const { expect } = require('chai');
+
+const Proxy = contract.fromArtifact('Proxy');
+const Implementation1 = contract.fromArtifact('Implementation1');
+const Implementation2 = contract.fromArtifact('Implementation2');
+const Implementation3 = contract.fromArtifact('Implementation3');
+const Implementation4 = contract.fromArtifact('Implementation4');
+const MigratableMockV1 = contract.fromArtifact('MigratableMockV1');
+const MigratableMockV2 = contract.fromArtifact('MigratableMockV2');
+const MigratableMockV3 = contract.fromArtifact('MigratableMockV3');
+const InitializableMock = contract.fromArtifact('InitializableMock');
+const DummyImplementation = contract.fromArtifact('DummyImplementation');
+const ClashingImplementation = contract.fromArtifact('ClashingImplementation');
+
+const IMPLEMENTATION_LABEL = 'eip1967.proxy.implementation';
+const ADMIN_LABEL = 'eip1967.proxy.admin';
+
+module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createProxy, accounts) {
+  const [proxyAdminAddress, proxyAdminOwner, anotherAccount] = accounts;
+
+  before(async function () {
+    this.implementationV0 = (await DummyImplementation.new()).address;
+    this.implementationV1 = (await DummyImplementation.new()).address;
+  });
+
+  beforeEach(async function () {
+    const initializeData = Buffer.from('');
+    this.proxy = await createProxy(this.implementationV0, proxyAdminAddress, initializeData, {
+      from: proxyAdminOwner,
+    });
+    this.proxyAddress = this.proxy.address;
+  });
+
+  describe('implementation', function () {
+    it('returns the current implementation address', async function () {
+      const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
+
+      expect(implementation).to.be.equal(this.implementationV0);
+    });
+
+    it('delegates to the implementation', async function () {
+      const dummy = new DummyImplementation(this.proxyAddress);
+      const value = await dummy.get();
+
+      expect(value).to.equal(true);
+    });
+  });
+
+  describe('upgradeTo', function () {
+    describe('when the sender is the admin', function () {
+      const from = proxyAdminAddress;
+
+      describe('when the given implementation is different from the current one', function () {
+        it('upgrades to the requested implementation', async function () {
+          await this.proxy.upgradeTo(this.implementationV1, { from });
+
+          const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
+          expect(implementation).to.be.equal(this.implementationV1);
+        });
+
+        it('emits an event', async function () {
+          expectEvent(
+            await this.proxy.upgradeTo(this.implementationV1, { from }),
+            'Upgraded', {
+              implementation: this.implementationV1,
+            },
+          );
+        });
+      });
+
+      describe('when the given implementation is the zero address', function () {
+        it('reverts', async function () {
+          await expectRevert(
+            this.proxy.upgradeTo(ZERO_ADDRESS, { from }),
+            'UpgradeableProxy: new implementation is not a contract',
+          );
+        });
+      });
+    });
+
+    describe('when the sender is not the admin', function () {
+      const from = anotherAccount;
+
+      it('reverts', async function () {
+        await expectRevert.unspecified(
+          this.proxy.upgradeTo(this.implementationV1, { from })
+        );
+      });
+    });
+  });
+
+  describe('upgradeToAndCall', function () {
+    describe('without migrations', function () {
+      beforeEach(async function () {
+        this.behavior = await InitializableMock.new();
+      });
+
+      describe('when the call does not fail', function () {
+        const initializeData = new InitializableMock('').contract.methods['initializeWithX(uint256)'](42).encodeABI();
+
+        describe('when the sender is the admin', function () {
+          const from = proxyAdminAddress;
+          const value = 1e5;
+
+          beforeEach(async function () {
+            this.receipt = await this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from, value });
+          });
+
+          it('upgrades to the requested implementation', async function () {
+            const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
+            expect(implementation).to.be.equal(this.behavior.address);
+          });
+
+          it('emits an event', function () {
+            expectEvent(this.receipt, 'Upgraded', { implementation: this.behavior.address });
+          });
+
+          it('calls the initializer function', async function () {
+            const migratable = new InitializableMock(this.proxyAddress);
+            const x = await migratable.x();
+            expect(x).to.be.bignumber.equal('42');
+          });
+
+          it('sends given value to the proxy', async function () {
+            const balance = await web3.eth.getBalance(this.proxyAddress);
+            expect(balance.toString()).to.be.bignumber.equal(value.toString());
+          });
+
+          it.skip('uses the storage of the proxy', async function () {
+            // storage layout should look as follows:
+            //  - 0: Initializable storage
+            //  - 1-50: Initailizable reserved storage (50 slots)
+            //  - 51: initializerRan
+            //  - 52: x
+            const storedValue = await Proxy.at(this.proxyAddress).getStorageAt(52);
+            expect(parseInt(storedValue)).to.eq(42);
+          });
+        });
+
+        describe('when the sender is not the admin', function () {
+          it('reverts', async function () {
+            await expectRevert.unspecified(
+              this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: anotherAccount }),
+            );
+          });
+        });
+      });
+
+      describe('when the call does fail', function () {
+        const initializeData = new InitializableMock('').contract.methods.fail().encodeABI();
+
+        it('reverts', async function () {
+          await expectRevert.unspecified(
+            this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: proxyAdminAddress }),
+          );
+        });
+      });
+    });
+
+    describe('with migrations', function () {
+      describe('when the sender is the admin', function () {
+        const from = proxyAdminAddress;
+        const value = 1e5;
+
+        describe('when upgrading to V1', function () {
+          const v1MigrationData = new MigratableMockV1('').contract.methods.initialize(42).encodeABI();
+
+          beforeEach(async function () {
+            this.behaviorV1 = await MigratableMockV1.new();
+            this.balancePreviousV1 = new BN(await web3.eth.getBalance(this.proxyAddress));
+            this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV1.address, v1MigrationData, { from, value });
+          });
+
+          it('upgrades to the requested version and emits an event', async function () {
+            const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
+            expect(implementation).to.be.equal(this.behaviorV1.address);
+            expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV1.address });
+          });
+
+          it('calls the \'initialize\' function and sends given value to the proxy', async function () {
+            const migratable = new MigratableMockV1(this.proxyAddress);
+
+            const x = await migratable.x();
+            expect(x).to.be.bignumber.equal('42');
+
+            const balance = await web3.eth.getBalance(this.proxyAddress);
+            expect(new BN(balance)).to.be.bignumber.equal(this.balancePreviousV1.addn(value));
+          });
+
+          describe('when upgrading to V2', function () {
+            const v2MigrationData = new MigratableMockV2('').contract.methods.migrate(10, 42).encodeABI();
+
+            beforeEach(async function () {
+              this.behaviorV2 = await MigratableMockV2.new();
+              this.balancePreviousV2 = new BN(await web3.eth.getBalance(this.proxyAddress));
+              this.receipt =
+                await this.proxy.upgradeToAndCall(this.behaviorV2.address, v2MigrationData, { from, value });
+            });
+
+            it('upgrades to the requested version and emits an event', async function () {
+              const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
+              expect(implementation).to.be.equal(this.behaviorV2.address);
+              expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV2.address });
+            });
+
+            it('calls the \'migrate\' function and sends given value to the proxy', async function () {
+              const migratable = new MigratableMockV2(this.proxyAddress);
+
+              const x = await migratable.x();
+              expect(x).to.be.bignumber.equal('10');
+
+              const y = await migratable.y();
+              expect(y).to.be.bignumber.equal('42');
+
+              const balance = new BN(await web3.eth.getBalance(this.proxyAddress));
+              expect(balance).to.be.bignumber.equal(this.balancePreviousV2.addn(value));
+            });
+
+            describe('when upgrading to V3', function () {
+              const v3MigrationData = new MigratableMockV3('').contract.methods['migrate()']().encodeABI();
+
+              beforeEach(async function () {
+                this.behaviorV3 = await MigratableMockV3.new();
+                this.balancePreviousV3 = new BN(await web3.eth.getBalance(this.proxyAddress));
+                this.receipt =
+                  await this.proxy.upgradeToAndCall(this.behaviorV3.address, v3MigrationData, { from, value });
+              });
+
+              it('upgrades to the requested version and emits an event', async function () {
+                const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress });
+                expect(implementation).to.be.equal(this.behaviorV3.address);
+                expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV3.address });
+              });
+
+              it('calls the \'migrate\' function and sends given value to the proxy', async function () {
+                const migratable = new MigratableMockV3(this.proxyAddress);
+
+                const x = await migratable.x();
+                expect(x).to.be.bignumber.equal('42');
+
+                const y = await migratable.y();
+                expect(y).to.be.bignumber.equal('10');
+
+                const balance = new BN(await web3.eth.getBalance(this.proxyAddress));
+                expect(balance).to.be.bignumber.equal(this.balancePreviousV3.addn(value));
+              });
+            });
+          });
+        });
+      });
+
+      describe('when the sender is not the admin', function () {
+        const from = anotherAccount;
+
+        it('reverts', async function () {
+          const behaviorV1 = await MigratableMockV1.new();
+          const v1MigrationData = new MigratableMockV1('').contract.methods.initialize(42).encodeABI();
+          await expectRevert.unspecified(
+            this.proxy.upgradeToAndCall(behaviorV1.address, v1MigrationData, { from }),
+          );
+        });
+      });
+    });
+  });
+
+  describe('changeAdmin', function () {
+    describe('when the new proposed admin is not the zero address', function () {
+      const newAdmin = anotherAccount;
+
+      describe('when the sender is the admin', function () {
+        beforeEach('transferring', async function () {
+          this.receipt = await this.proxy.changeAdmin(newAdmin, { from: proxyAdminAddress });
+        });
+
+        it('assigns new proxy admin', async function () {
+          const newProxyAdmin = await this.proxy.admin.call({ from: newAdmin });
+          expect(newProxyAdmin).to.be.equal(anotherAccount);
+        });
+
+        it('emits an event', function () {
+          expectEvent(this.receipt, 'AdminChanged', {
+            previousAdmin: proxyAdminAddress,
+            newAdmin: newAdmin,
+          });
+        });
+      });
+
+      describe('when the sender is not the admin', function () {
+        it('reverts', async function () {
+          await expectRevert.unspecified(this.proxy.changeAdmin(newAdmin, { from: anotherAccount }));
+        });
+      });
+    });
+
+    describe('when the new proposed admin is the zero address', function () {
+      it('reverts', async function () {
+        await expectRevert(
+          this.proxy.changeAdmin(ZERO_ADDRESS, { from: proxyAdminAddress }),
+          'TransparentUpgradeableProxy: new admin is the zero address',
+        );
+      });
+    });
+  });
+
+  describe('storage', function () {
+    it('should store the implementation address in specified location', async function () {
+      const slot = '0x' + new BN(keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16);
+      const implementation = toChecksumAddress(await web3.eth.getStorageAt(this.proxyAddress, slot));
+      expect(implementation).to.be.equal(this.implementationV0);
+    });
+
+    it('should store the admin proxy in specified location', async function () {
+      const slot = '0x' + new BN(keccak256(Buffer.from(ADMIN_LABEL))).subn(1).toString(16);
+      const proxyAdmin = toChecksumAddress(await web3.eth.getStorageAt(this.proxyAddress, slot));
+      expect(proxyAdmin).to.be.equal(proxyAdminAddress);
+    });
+  });
+
+  describe('transparent proxy', function () {
+    beforeEach('creating proxy', async function () {
+      const initializeData = Buffer.from('');
+      this.impl = await ClashingImplementation.new();
+      this.proxy = await createProxy(this.impl.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
+
+      this.clashing = new ClashingImplementation(this.proxy.address);
+    });
+
+    it('proxy admin cannot call delegated functions', async function () {
+      await expectRevert(
+        this.clashing.delegatedFunction({ from: proxyAdminAddress }),
+        'TransparentUpgradeableProxy: admin cannot fallback to proxy target',
+      );
+    });
+
+    context('when function names clash', function () {
+      it('when sender is proxy admin should run the proxy function', async function () {
+        const value = await this.proxy.admin.call({ from: proxyAdminAddress });
+        expect(value).to.be.equal(proxyAdminAddress);
+      });
+
+      it('when sender is other should delegate to implementation', async function () {
+        const value = await this.proxy.admin.call({ from: anotherAccount });
+        expect(value).to.be.equal('0x0000000000000000000000000000000011111142');
+      });
+    });
+  });
+
+  describe('regression', () => {
+    const initializeData = Buffer.from('');
+
+    it('should add new function', async () => {
+      const instance1 = await Implementation1.new();
+      const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
+
+      const proxyInstance1 = new Implementation1(proxy.address);
+      await proxyInstance1.setValue(42);
+
+      const instance2 = await Implementation2.new();
+      await proxy.upgradeTo(instance2.address, { from: proxyAdminAddress });
+
+      const proxyInstance2 = new Implementation2(proxy.address);
+      const res = await proxyInstance2.getValue();
+      expect(res.toString()).to.eq('42');
+    });
+
+    it('should remove function', async () => {
+      const instance2 = await Implementation2.new();
+      const proxy = await createProxy(instance2.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
+
+      const proxyInstance2 = new Implementation2(proxy.address);
+      await proxyInstance2.setValue(42);
+      const res = await proxyInstance2.getValue();
+      expect(res.toString()).to.eq('42');
+
+      const instance1 = await Implementation1.new();
+      await proxy.upgradeTo(instance1.address, { from: proxyAdminAddress });
+
+      const proxyInstance1 = new Implementation2(proxy.address);
+      await expectRevert.unspecified(proxyInstance1.getValue());
+    });
+
+    it('should change function signature', async () => {
+      const instance1 = await Implementation1.new();
+      const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
+
+      const proxyInstance1 = new Implementation1(proxy.address);
+      await proxyInstance1.setValue(42);
+
+      const instance3 = await Implementation3.new();
+      await proxy.upgradeTo(instance3.address, { from: proxyAdminAddress });
+      const proxyInstance3 = new Implementation3(proxy.address);
+
+      const res = await proxyInstance3.getValue(8);
+      expect(res.toString()).to.eq('50');
+    });
+
+    it('should add fallback function', async () => {
+      const initializeData = Buffer.from('');
+      const instance1 = await Implementation1.new();
+      const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
+
+      const instance4 = await Implementation4.new();
+      await proxy.upgradeTo(instance4.address, { from: proxyAdminAddress });
+      const proxyInstance4 = new Implementation4(proxy.address);
+
+      const data = '';
+      await web3.eth.sendTransaction({ to: proxy.address, from: anotherAccount, data });
+
+      const res = await proxyInstance4.getValue();
+      expect(res.toString()).to.eq('1');
+    });
+
+    it('should remove fallback function', async () => {
+      const instance4 = await Implementation4.new();
+      const proxy = await createProxy(instance4.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner });
+
+      const instance2 = await Implementation2.new();
+      await proxy.upgradeTo(instance2.address, { from: proxyAdminAddress });
+
+      const data = '';
+      await expectRevert.unspecified(
+        web3.eth.sendTransaction({ to: proxy.address, from: anotherAccount, data }),
+      );
+
+      const proxyInstance2 = new Implementation2(proxy.address);
+      const res = await proxyInstance2.getValue();
+      expect(res.toString()).to.eq('0');
+    });
+  });
+};

+ 17 - 0
test/proxy/TransparentUpgradeableProxy.test.js

@@ -0,0 +1,17 @@
+const { accounts, contract } = require('@openzeppelin/test-environment');
+
+const shouldBehaveLikeUpgradeableProxy = require('./UpgradeableProxy.behaviour');
+const shouldBehaveLikeTransparentUpgradeableProxy = require('./TransparentUpgradeableProxy.behaviour');
+
+const TransparentUpgradeableProxy = contract.fromArtifact('TransparentUpgradeableProxy');
+
+describe('TransparentUpgradeableProxy', function () {
+  const [proxyAdminAddress, proxyAdminOwner] = accounts;
+
+  const createProxy = async function (logic, admin, initData, opts) {
+    return TransparentUpgradeableProxy.new(logic, admin, initData, opts);
+  };
+
+  shouldBehaveLikeUpgradeableProxy(createProxy, proxyAdminAddress, proxyAdminOwner);
+  shouldBehaveLikeTransparentUpgradeableProxy(createProxy, accounts);
+});

+ 216 - 0
test/proxy/UpgradeableProxy.behaviour.js

@@ -0,0 +1,216 @@
+const { contract, web3 } = require('@openzeppelin/test-environment');
+
+const { BN, expectRevert } = require('@openzeppelin/test-helpers');
+const { toChecksumAddress, keccak256 } = require('ethereumjs-util');
+
+const { expect } = require('chai');
+
+const DummyImplementation = contract.fromArtifact('DummyImplementation');
+
+const IMPLEMENTATION_LABEL = 'eip1967.proxy.implementation';
+
+module.exports = function shouldBehaveLikeUpgradeableProxy (createProxy, proxyAdminAddress, proxyCreator) {
+  it('cannot be initialized with a non-contract address', async function () {
+    const nonContractAddress = proxyCreator;
+    const initializeData = Buffer.from('');
+    await expectRevert.unspecified(
+      createProxy(nonContractAddress, proxyAdminAddress, initializeData, {
+        from: proxyCreator,
+      }),
+    );
+  });
+
+  before('deploy implementation', async function () {
+    this.implementation = web3.utils.toChecksumAddress((await DummyImplementation.new()).address);
+  });
+
+  const assertProxyInitialization = function ({ value, balance }) {
+    it('sets the implementation address', async function () {
+      const slot = '0x' + new BN(keccak256(Buffer.from(IMPLEMENTATION_LABEL))).subn(1).toString(16);
+      const implementation = toChecksumAddress(await web3.eth.getStorageAt(this.proxy, slot));
+      expect(implementation).to.be.equal(this.implementation);
+    });
+
+    it('initializes the proxy', async function () {
+      const dummy = new DummyImplementation(this.proxy);
+      expect(await dummy.value()).to.be.bignumber.equal(value.toString());
+    });
+
+    it('has expected balance', async function () {
+      expect(await web3.eth.getBalance(this.proxy)).to.be.bignumber.equal(balance.toString());
+    });
+  };
+
+  describe('without initialization', function () {
+    const initializeData = Buffer.from('');
+
+    describe('when not sending balance', function () {
+      beforeEach('creating proxy', async function () {
+        this.proxy = (
+          await createProxy(this.implementation, proxyAdminAddress, initializeData, {
+            from: proxyCreator,
+          })
+        ).address;
+      });
+
+      assertProxyInitialization({ value: 0, balance: 0 });
+    });
+
+    describe('when sending some balance', function () {
+      const value = 10e5;
+
+      beforeEach('creating proxy', async function () {
+        this.proxy = (
+          await createProxy(this.implementation, proxyAdminAddress, initializeData, {
+            from: proxyCreator,
+            value,
+          })
+        ).address;
+      });
+
+      assertProxyInitialization({ value: 0, balance: value });
+    });
+  });
+
+  describe('initialization without parameters', function () {
+    describe('non payable', function () {
+      const expectedInitializedValue = 10;
+      const initializeData = new DummyImplementation('').contract.methods['initializeNonPayable()']().encodeABI();
+
+      describe('when not sending balance', function () {
+        beforeEach('creating proxy', async function () {
+          this.proxy = (
+            await createProxy(this.implementation, proxyAdminAddress, initializeData, {
+              from: proxyCreator,
+            })
+          ).address;
+        });
+
+        assertProxyInitialization({
+          value: expectedInitializedValue,
+          balance: 0,
+        });
+      });
+
+      describe('when sending some balance', function () {
+        const value = 10e5;
+
+        it('reverts', async function () {
+          await expectRevert.unspecified(
+            createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator, value }),
+          );
+        });
+      });
+    });
+
+    describe('payable', function () {
+      const expectedInitializedValue = 100;
+      const initializeData = new DummyImplementation('').contract.methods['initializePayable()']().encodeABI();
+
+      describe('when not sending balance', function () {
+        beforeEach('creating proxy', async function () {
+          this.proxy = (
+            await createProxy(this.implementation, proxyAdminAddress, initializeData, {
+              from: proxyCreator,
+            })
+          ).address;
+        });
+
+        assertProxyInitialization({
+          value: expectedInitializedValue,
+          balance: 0,
+        });
+      });
+
+      describe('when sending some balance', function () {
+        const value = 10e5;
+
+        beforeEach('creating proxy', async function () {
+          this.proxy = (
+            await createProxy(this.implementation, proxyAdminAddress, initializeData, {
+              from: proxyCreator,
+              value,
+            })
+          ).address;
+        });
+
+        assertProxyInitialization({
+          value: expectedInitializedValue,
+          balance: value,
+        });
+      });
+    });
+  });
+
+  describe('initialization with parameters', function () {
+    describe('non payable', function () {
+      const expectedInitializedValue = 10;
+      const initializeData = new DummyImplementation('').contract
+        .methods['initializeNonPayable(uint256)'](expectedInitializedValue).encodeABI();
+
+      describe('when not sending balance', function () {
+        beforeEach('creating proxy', async function () {
+          this.proxy = (
+            await createProxy(this.implementation, proxyAdminAddress, initializeData, {
+              from: proxyCreator,
+            })
+          ).address;
+        });
+
+        assertProxyInitialization({
+          value: expectedInitializedValue,
+          balance: 0,
+        });
+      });
+
+      describe('when sending some balance', function () {
+        const value = 10e5;
+
+        it('reverts', async function () {
+          await expectRevert.unspecified(
+            createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator, value }),
+          );
+        });
+      });
+    });
+
+    describe('payable', function () {
+      const expectedInitializedValue = 42;
+      const initializeData = new DummyImplementation('').contract
+        .methods['initializePayable(uint256)'](expectedInitializedValue).encodeABI();
+
+      describe('when not sending balance', function () {
+        beforeEach('creating proxy', async function () {
+          this.proxy = (
+            await createProxy(this.implementation, proxyAdminAddress, initializeData, {
+              from: proxyCreator,
+            })
+          ).address;
+        });
+
+        assertProxyInitialization({
+          value: expectedInitializedValue,
+          balance: 0,
+        });
+      });
+
+      describe('when sending some balance', function () {
+        const value = 10e5;
+
+        beforeEach('creating proxy', async function () {
+          this.proxy = (
+            await createProxy(this.implementation, proxyAdminAddress, initializeData, {
+              from: proxyCreator,
+              value,
+            })
+          ).address;
+        });
+
+        assertProxyInitialization({
+          value: expectedInitializedValue,
+          balance: value,
+        });
+      });
+    });
+  });
+};

+ 15 - 0
test/proxy/UpgradeableProxy.test.js

@@ -0,0 +1,15 @@
+const { accounts, contract } = require('@openzeppelin/test-environment');
+
+const shouldBehaveLikeUpgradeableProxy = require('./UpgradeableProxy.behaviour');
+
+const UpgradeableProxy = contract.fromArtifact('UpgradeableProxy');
+
+describe('UpgradeableProxy', function () {
+  const [proxyAdminOwner] = accounts;
+
+  const createProxy = async function (implementation, _admin, initData, opts) {
+    return UpgradeableProxy.new(implementation, initData, opts);
+  };
+
+  shouldBehaveLikeUpgradeableProxy(createProxy, undefined, proxyAdminOwner);
+});

+ 74 - 6
test/token/ERC1155/ERC1155.behavior.js

@@ -92,6 +92,14 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
           ),
           'ERC1155: accounts and ids length mismatch'
         );
+
+        await expectRevert(
+          this.token.balanceOfBatch(
+            [firstTokenHolder, secondTokenHolder],
+            [firstTokenId, secondTokenId, unknownTokenId]
+          ),
+          'ERC1155: accounts and ids length mismatch'
+        );
       });
 
       it('reverts when one of the addresses is the zero address', async function () {
@@ -143,6 +151,18 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
           expect(result[1]).to.be.a.bignumber.equal(firstAmount);
           expect(result[2]).to.be.a.bignumber.equal('0');
         });
+
+        it('returns multiple times the balance of the same address when asked', async function () {
+          const result = await this.token.balanceOfBatch(
+            [firstTokenHolder, secondTokenHolder, firstTokenHolder],
+            [firstTokenId, secondTokenId, firstTokenId]
+          );
+          expect(result).to.be.an('array');
+          expect(result[0]).to.be.a.bignumber.equal(result[2]);
+          expect(result[0]).to.be.a.bignumber.equal(firstAmount);
+          expect(result[1]).to.be.a.bignumber.equal(secondAmount);
+          expect(result[2]).to.be.a.bignumber.equal(firstAmount);
+        });
       });
     });
 
@@ -298,8 +318,11 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
           });
 
           it('preserves operator\'s balances not involved in the transfer', async function () {
-            const balance = await this.token.balanceOf(proxy, firstTokenId);
-            expect(balance).to.be.a.bignumber.equal('0');
+            const balance1 = await this.token.balanceOf(proxy, firstTokenId);
+            expect(balance1).to.be.a.bignumber.equal('0');
+
+            const balance2 = await this.token.balanceOf(proxy, secondTokenId);
+            expect(balance2).to.be.a.bignumber.equal('0');
           });
         });
       });
@@ -333,7 +356,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
             value: firstAmount,
           });
 
-          it('should call onERC1155Received', async function () {
+          it('calls onERC1155Received', async function () {
             await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'Received', {
               operator: multiTokenHolder,
               from: multiTokenHolder,
@@ -366,7 +389,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
             value: firstAmount,
           });
 
-          it('should call onERC1155Received', async function () {
+          it('calls onERC1155Received', async function () {
             await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'Received', {
               operator: multiTokenHolder,
               from: multiTokenHolder,
@@ -464,6 +487,16 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
           ),
           'ERC1155: ids and amounts length mismatch'
         );
+
+        await expectRevert(
+          this.token.safeBatchTransferFrom(
+            multiTokenHolder, recipient,
+            [firstTokenId, secondTokenId],
+            [firstAmount],
+            '0x', { from: multiTokenHolder }
+          ),
+          'ERC1155: ids and amounts length mismatch'
+        );
       });
 
       it('reverts when transferring to zero address', async function () {
@@ -599,7 +632,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
             values: [firstAmount, secondAmount],
           });
 
-          it('should call onERC1155BatchReceived', async function () {
+          it('calls onERC1155BatchReceived', async function () {
             await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', {
               operator: multiTokenHolder,
               from: multiTokenHolder,
@@ -630,7 +663,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
             values: [firstAmount, secondAmount],
           });
 
-          it('should call onERC1155Received', async function () {
+          it('calls onERC1155Received', async function () {
             await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', {
               operator: multiTokenHolder,
               from: multiTokenHolder,
@@ -684,6 +717,41 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
         });
       });
 
+      context('to a receiver contract that reverts only on single transfers', function () {
+        beforeEach(async function () {
+          this.receiver = await ERC1155ReceiverMock.new(
+            RECEIVER_SINGLE_MAGIC_VALUE, true,
+            RECEIVER_BATCH_MAGIC_VALUE, false,
+          );
+
+          this.toWhom = this.receiver.address;
+          this.transferReceipt = await this.token.safeBatchTransferFrom(
+            multiTokenHolder, this.receiver.address,
+            [firstTokenId, secondTokenId],
+            [firstAmount, secondAmount],
+            '0x', { from: multiTokenHolder },
+          );
+          ({ logs: this.transferLogs } = this.transferReceipt);
+        });
+
+        batchTransferWasSuccessful.call(this, {
+          operator: multiTokenHolder,
+          from: multiTokenHolder,
+          ids: [firstTokenId, secondTokenId],
+          values: [firstAmount, secondAmount],
+        });
+
+        it('calls onERC1155BatchReceived', async function () {
+          await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', {
+            operator: multiTokenHolder,
+            from: multiTokenHolder,
+            // ids: [firstTokenId, secondTokenId],
+            // values: [firstAmount, secondAmount],
+            data: null,
+          });
+        });
+      });
+
       context('to a contract that does not implement the required function', function () {
         it('reverts', async function () {
           const invalidReceiver = this.token;

+ 26 - 1
test/token/ERC1155/ERC1155.test.js

@@ -28,7 +28,7 @@ describe('ERC1155', function () {
     const mintAmounts = [new BN(5000), new BN(10000), new BN(42195)];
     const burnAmounts = [new BN(5000), new BN(9001), new BN(195)];
 
-    const data = '0xcafebabe';
+    const data = '0x12345678';
 
     describe('_mint', function () {
       it('reverts with a zero destination address', async function () {
@@ -72,6 +72,11 @@ describe('ERC1155', function () {
           this.token.mintBatch(tokenBatchHolder, tokenBatchIds, mintAmounts.slice(1), data),
           'ERC1155: ids and amounts length mismatch'
         );
+
+        await expectRevert(
+          this.token.mintBatch(tokenBatchHolder, tokenBatchIds.slice(1), mintAmounts, data),
+          'ERC1155: ids and amounts length mismatch'
+        );
       });
 
       context('with minted batch of tokens', function () {
@@ -121,6 +126,21 @@ describe('ERC1155', function () {
         );
       });
 
+      it('reverts when burning more than available tokens', async function () {
+        await this.token.mint(
+          tokenHolder,
+          tokenId,
+          mintAmount,
+          data,
+          { from: operator }
+        );
+
+        await expectRevert(
+          this.token.burn(tokenHolder, tokenId, mintAmount.addn(1)),
+          'ERC1155: burn amount exceeds balance'
+        );
+      });
+
       context('with minted-then-burnt tokens', function () {
         beforeEach(async function () {
           await this.token.mint(tokenHolder, tokenId, mintAmount, data);
@@ -164,6 +184,11 @@ describe('ERC1155', function () {
           this.token.burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts.slice(1)),
           'ERC1155: ids and amounts length mismatch'
         );
+
+        await expectRevert(
+          this.token.burnBatch(tokenBatchHolder, tokenBatchIds.slice(1), burnAmounts),
+          'ERC1155: ids and amounts length mismatch'
+        );
       });
 
       it('reverts when burning a non-existent token id', async function () {

+ 32 - 21
test/token/ERC1155/ERC1155Holder.test.js

@@ -8,41 +8,52 @@ const { expect } = require('chai');
 
 describe('ERC1155Holder', function () {
   const [creator] = accounts;
+  const uri = 'https://token-cdn-domain/{id}.json';
+  const multiTokenIds = [new BN(1), new BN(2), new BN(3)];
+  const multiTokenAmounts = [new BN(1000), new BN(2000), new BN(3000)];
+  const transferData = '0x12345678';
+
+  beforeEach(async function () {
+    this.multiToken = await ERC1155Mock.new(uri, { from: creator });
+    this.holder = await ERC1155Holder.new();
+    await this.multiToken.mintBatch(creator, multiTokenIds, multiTokenAmounts, '0x', { from: creator });
+  });
 
-  it('receives ERC1155 tokens', async function () {
-    const uri = 'https://token-cdn-domain/{id}.json';
-
-    const multiToken = await ERC1155Mock.new(uri, { from: creator });
-    const multiTokenIds = [new BN(1), new BN(2), new BN(3)];
-    const multiTokenAmounts = [new BN(1000), new BN(2000), new BN(3000)];
-    await multiToken.mintBatch(creator, multiTokenIds, multiTokenAmounts, '0x', { from: creator });
-
-    const transferData = '0xf00dbabe';
-
-    const holder = await ERC1155Holder.new();
-
-    await multiToken.safeTransferFrom(
+  it('receives ERC1155 tokens from a single ID', async function () {
+    await this.multiToken.safeTransferFrom(
       creator,
-      holder.address,
+      this.holder.address,
       multiTokenIds[0],
       multiTokenAmounts[0],
       transferData,
       { from: creator },
     );
 
-    expect(await multiToken.balanceOf(holder.address, multiTokenIds[0])).to.be.bignumber.equal(multiTokenAmounts[0]);
+    expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[0]))
+      .to.be.bignumber.equal(multiTokenAmounts[0]);
 
-    await multiToken.safeBatchTransferFrom(
+    for (let i = 1; i < multiTokenIds.length; i++) {
+      expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[i])).to.be.bignumber.equal(new BN(0));
+    }
+  });
+
+  it('receives ERC1155 tokens from a multiple IDs', async function () {
+    for (let i = 0; i < multiTokenIds.length; i++) {
+      expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[i])).to.be.bignumber.equal(new BN(0));
+    };
+
+    await this.multiToken.safeBatchTransferFrom(
       creator,
-      holder.address,
-      multiTokenIds.slice(1),
-      multiTokenAmounts.slice(1),
+      this.holder.address,
+      multiTokenIds,
+      multiTokenAmounts,
       transferData,
       { from: creator },
     );
 
-    for (let i = 1; i < multiTokenIds.length; i++) {
-      expect(await multiToken.balanceOf(holder.address, multiTokenIds[i])).to.be.bignumber.equal(multiTokenAmounts[i]);
+    for (let i = 0; i < multiTokenIds.length; i++) {
+      expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[i]))
+        .to.be.bignumber.equal(multiTokenAmounts[i]);
     }
   });
 });

+ 1 - 1
test/token/ERC20/ERC20Snapshot.test.js

@@ -134,7 +134,7 @@ describe('ERC20Snapshot', function () {
       context('with balance changes after the snapshot', function () {
         beforeEach(async function () {
           await this.token.transfer(recipient, new BN('10'), { from: initialHolder });
-          await this.token.mint(recipient, new BN('50'));
+          await this.token.mint(other, new BN('50'));
           await this.token.burn(initialHolder, new BN('20'));
         });
 

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

@@ -6,21 +6,21 @@ function shouldBehaveLikeERC20Capped (minter, [other], cap) {
   describe('capped token', function () {
     const from = minter;
 
-    it('should start with the correct cap', async function () {
+    it('starts with the correct cap', async function () {
       expect(await this.token.cap()).to.be.bignumber.equal(cap);
     });
 
-    it('should mint when amount is less than cap', async function () {
+    it('mints when amount is less than cap', async function () {
       await this.token.mint(other, cap.subn(1), { from });
       expect(await this.token.totalSupply()).to.be.bignumber.equal(cap.subn(1));
     });
 
-    it('should fail to mint if the amount exceeds the cap', async function () {
+    it('fails to mint if the amount exceeds the cap', async function () {
       await this.token.mint(other, cap.subn(1), { from });
       await expectRevert(this.token.mint(other, 2, { from }), 'ERC20Capped: cap exceeded');
     });
 
-    it('should fail to mint after cap is reached', async function () {
+    it('fails to mint after cap is reached', async function () {
       await this.token.mint(other, cap, { from });
       await expectRevert(this.token.mint(other, 1, { from }), 'ERC20Capped: cap exceeded');
     });

+ 8 - 8
test/token/ERC721/ERC721.test.js

@@ -332,7 +332,7 @@ describe('ERC721', function () {
 
             shouldTransferTokensByUsers(transferFun);
 
-            it('should call onERC721Received', async function () {
+            it('calls onERC721Received', async function () {
               const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: owner });
 
               await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
@@ -343,7 +343,7 @@ describe('ERC721', function () {
               });
             });
 
-            it('should call onERC721Received from approved', async function () {
+            it('calls onERC721Received from approved', async function () {
               const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: approved });
 
               await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
@@ -417,7 +417,7 @@ describe('ERC721', function () {
       const data = '0x42';
 
       describe('via safeMint', function () { // regular minting is tested in ERC721Mintable.test.js and others
-        it('should call onERC721Received — with data', async function () {
+        it('calls onERC721Received — with data', async function () {
           this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, false);
           const receipt = await this.token.safeMint(this.receiver.address, tokenId, data);
 
@@ -428,7 +428,7 @@ describe('ERC721', function () {
           });
         });
 
-        it('should call onERC721Received — without data', async function () {
+        it('calls onERC721Received — without data', async function () {
           this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, false);
           const receipt = await this.token.safeMint(this.receiver.address, tokenId);
 
@@ -691,7 +691,7 @@ describe('ERC721', function () {
             await this.token.approve(approved, firstTokenId, { from: owner });
           });
 
-          it('should return approved account', async function () {
+          it('returns approved account', async function () {
             expect(await this.token.getApproved(firstTokenId)).to.be.equal(approved);
           });
         });
@@ -752,7 +752,7 @@ describe('ERC721', function () {
     });
 
     describe('tokenByIndex', function () {
-      it('should return all tokens', async function () {
+      it('returns all tokens', async function () {
         const tokensListed = await Promise.all(
           [0, 1].map(i => this.token.tokenByIndex(i))
         );
@@ -760,14 +760,14 @@ describe('ERC721', function () {
           secondTokenId.toNumber()]);
       });
 
-      it('should revert if index is greater than supply', async function () {
+      it('reverts if index is greater than supply', async function () {
         await expectRevert(
           this.token.tokenByIndex(2), 'EnumerableMap: index out of bounds'
         );
       });
 
       [firstTokenId, secondTokenId].forEach(function (tokenId) {
-        it(`should return all tokens after burning token ${tokenId} and minting new tokens`, async function () {
+        it(`returns all tokens after burning token ${tokenId} and minting new tokens`, async function () {
           const newTokenId = new BN(300);
           const anotherNewTokenId = new BN(400);
 

+ 1 - 1
test/token/ERC721/ERC721Pausable.test.js

@@ -86,7 +86,7 @@ describe('ERC721Pausable', function () {
     });
 
     describe('exists', function () {
-      it('should return token existence', async function () {
+      it('returns token existence', async function () {
         expect(await this.token.exists(firstTokenId)).to.equal(true);
       });
     });

+ 2 - 2
test/utils/Address.test.js

@@ -15,11 +15,11 @@ describe('Address', function () {
   });
 
   describe('isContract', function () {
-    it('should return false for account address', async function () {
+    it('returns false for account address', async function () {
       expect(await this.mock.isContract(other)).to.equal(false);
     });
 
-    it('should return true for contract address', async function () {
+    it('returns true for contract address', async function () {
       const contract = await AddressImpl.new();
       expect(await this.mock.isContract(contract.address)).to.equal(true);
     });

+ 56 - 54
test/utils/Arrays.test.js

@@ -6,81 +6,83 @@ const { expect } = require('chai');
 const ArraysImpl = contract.fromArtifact('ArraysImpl');
 
 describe('Arrays', function () {
-  context('Even number of elements', function () {
-    const EVEN_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
+  describe('findUpperBound', function () {
+    context('Even number of elements', function () {
+      const EVEN_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
 
-    beforeEach(async function () {
-      this.arrays = await ArraysImpl.new(EVEN_ELEMENTS_ARRAY);
-    });
+      beforeEach(async function () {
+        this.arrays = await ArraysImpl.new(EVEN_ELEMENTS_ARRAY);
+      });
 
-    it('should return correct index for the basic case', async function () {
-      expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5');
-    });
+      it('returns correct index for the basic case', async function () {
+        expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5');
+      });
 
-    it('should return 0 for the first element', async function () {
-      expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0');
-    });
+      it('returns 0 for the first element', async function () {
+        expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0');
+      });
 
-    it('should return index of the last element', async function () {
-      expect(await this.arrays.findUpperBound(20)).to.be.bignumber.equal('9');
-    });
+      it('returns index of the last element', async function () {
+        expect(await this.arrays.findUpperBound(20)).to.be.bignumber.equal('9');
+      });
 
-    it('should return first index after last element if searched value is over the upper boundary', async function () {
-      expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('10');
-    });
+      it('returns first index after last element if searched value is over the upper boundary', async function () {
+        expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('10');
+      });
 
-    it('should return 0 for the element under the lower boundary', async function () {
-      expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0');
+      it('returns 0 for the element under the lower boundary', async function () {
+        expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0');
+      });
     });
-  });
 
-  context('Odd number of elements', function () {
-    const ODD_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21];
+    context('Odd number of elements', function () {
+      const ODD_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21];
 
-    beforeEach(async function () {
-      this.arrays = await ArraysImpl.new(ODD_ELEMENTS_ARRAY);
-    });
+      beforeEach(async function () {
+        this.arrays = await ArraysImpl.new(ODD_ELEMENTS_ARRAY);
+      });
 
-    it('should return correct index for the basic case', async function () {
-      expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5');
-    });
+      it('returns correct index for the basic case', async function () {
+        expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5');
+      });
 
-    it('should return 0 for the first element', async function () {
-      expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0');
-    });
+      it('returns 0 for the first element', async function () {
+        expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0');
+      });
 
-    it('should return index of the last element', async function () {
-      expect(await this.arrays.findUpperBound(21)).to.be.bignumber.equal('10');
-    });
+      it('returns index of the last element', async function () {
+        expect(await this.arrays.findUpperBound(21)).to.be.bignumber.equal('10');
+      });
 
-    it('should return first index after last element if searched value is over the upper boundary', async function () {
-      expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('11');
-    });
+      it('returns first index after last element if searched value is over the upper boundary', async function () {
+        expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('11');
+      });
 
-    it('should return 0 for the element under the lower boundary', async function () {
-      expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0');
+      it('returns 0 for the element under the lower boundary', async function () {
+        expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0');
+      });
     });
-  });
 
-  context('Array with gap', function () {
-    const WITH_GAP_ARRAY = [11, 12, 13, 14, 15, 20, 21, 22, 23, 24];
+    context('Array with gap', function () {
+      const WITH_GAP_ARRAY = [11, 12, 13, 14, 15, 20, 21, 22, 23, 24];
 
-    beforeEach(async function () {
-      this.arrays = await ArraysImpl.new(WITH_GAP_ARRAY);
-    });
+      beforeEach(async function () {
+        this.arrays = await ArraysImpl.new(WITH_GAP_ARRAY);
+      });
 
-    it('should return index of first element in next filled range', async function () {
-      expect(await this.arrays.findUpperBound(17)).to.be.bignumber.equal('5');
+      it('returns index of first element in next filled range', async function () {
+        expect(await this.arrays.findUpperBound(17)).to.be.bignumber.equal('5');
+      });
     });
-  });
 
-  context('Empty array', function () {
-    beforeEach(async function () {
-      this.arrays = await ArraysImpl.new([]);
-    });
+    context('Empty array', function () {
+      beforeEach(async function () {
+        this.arrays = await ArraysImpl.new([]);
+      });
 
-    it('should always return 0 for empty array', async function () {
-      expect(await this.arrays.findUpperBound(10)).to.be.bignumber.equal('0');
+      it('always returns 0 for empty array', async function () {
+        expect(await this.arrays.findUpperBound(10)).to.be.bignumber.equal('0');
+      });
     });
   });
 });

+ 30 - 26
test/utils/Counters.test.js

@@ -15,17 +15,19 @@ describe('Counters', function () {
   });
 
   describe('increment', function () {
-    it('increments the current value by one', async function () {
-      await this.counter.increment();
-      expect(await this.counter.current()).to.be.bignumber.equal('1');
-    });
+    context('starting from 0', function () {
+      it('increments the current value by one', async function () {
+        await this.counter.increment();
+        expect(await this.counter.current()).to.be.bignumber.equal('1');
+      });
 
-    it('can be called multiple times', async function () {
-      await this.counter.increment();
-      await this.counter.increment();
-      await this.counter.increment();
+      it('can be called multiple times', async function () {
+        await this.counter.increment();
+        await this.counter.increment();
+        await this.counter.increment();
 
-      expect(await this.counter.current()).to.be.bignumber.equal('3');
+        expect(await this.counter.current()).to.be.bignumber.equal('3');
+      });
     });
   });
 
@@ -34,28 +36,30 @@ describe('Counters', function () {
       await this.counter.increment();
       expect(await this.counter.current()).to.be.bignumber.equal('1');
     });
+    context('starting from 1', function () {
+      it('decrements the current value by one', async function () {
+        await this.counter.decrement();
+        expect(await this.counter.current()).to.be.bignumber.equal('0');
+      });
 
-    it('decrements the current value by one', async function () {
-      await this.counter.decrement();
-      expect(await this.counter.current()).to.be.bignumber.equal('0');
+      it('reverts if the current value is 0', async function () {
+        await this.counter.decrement();
+        await expectRevert(this.counter.decrement(), 'SafeMath: subtraction overflow');
+      });
     });
+    context('after incremented to 3', function () {
+      it('can be called multiple times', async function () {
+        await this.counter.increment();
+        await this.counter.increment();
 
-    it('reverts if the current value is 0', async function () {
-      await this.counter.decrement();
-      await expectRevert(this.counter.decrement(), 'SafeMath: subtraction overflow');
-    });
-
-    it('can be called multiple times', async function () {
-      await this.counter.increment();
-      await this.counter.increment();
-
-      expect(await this.counter.current()).to.be.bignumber.equal('3');
+        expect(await this.counter.current()).to.be.bignumber.equal('3');
 
-      await this.counter.decrement();
-      await this.counter.decrement();
-      await this.counter.decrement();
+        await this.counter.decrement();
+        await this.counter.decrement();
+        await this.counter.decrement();
 
-      expect(await this.counter.current()).to.be.bignumber.equal('0');
+        expect(await this.counter.current()).to.be.bignumber.equal('0');
+      });
     });
   });
 });

+ 64 - 63
test/utils/Create2.test.js

@@ -23,71 +23,72 @@ describe('Create2', function () {
   beforeEach(async function () {
     this.factory = await Create2Impl.new();
   });
-
-  it('should compute the correct contract address', async function () {
-    const onChainComputed = await this.factory
-      .computeAddress(saltHex, web3.utils.keccak256(constructorByteCode));
-    const offChainComputed =
-      computeCreate2Address(saltHex, constructorByteCode, this.factory.address);
-    expect(onChainComputed).to.equal(offChainComputed);
-  });
-
-  it('should compute the correct contract address with deployer', async function () {
-    const onChainComputed = await this.factory
-      .computeAddressWithDeployer(saltHex, web3.utils.keccak256(constructorByteCode), deployerAccount);
-    const offChainComputed =
-      computeCreate2Address(saltHex, constructorByteCode, deployerAccount);
-    expect(onChainComputed).to.equal(offChainComputed);
-  });
-
-  it('should deploy a ERC1820Implementer from inline assembly code', async function () {
-    const offChainComputed =
-      computeCreate2Address(saltHex, ERC1820Implementer.bytecode, this.factory.address);
-
-    await this.factory.deployERC1820Implementer(0, saltHex);
-
-    expect(ERC1820Implementer.bytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2));
-  });
-
-  it('should deploy a ERC20Mock with correct balances', async function () {
-    const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address);
-
-    await this.factory.deploy(0, saltHex, constructorByteCode);
-
-    const erc20 = await ERC20Mock.at(offChainComputed);
-    expect(await erc20.balanceOf(deployerAccount)).to.be.bignumber.equal(new BN(100));
-  });
-
-  it('should deploy a contract with funds deposited in the factory', async function () {
-    const deposit = ether('2');
-    await send.ether(deployerAccount, this.factory.address, deposit);
-    expect(await balance.current(this.factory.address)).to.be.bignumber.equal(deposit);
-
-    const onChainComputed = await this.factory
-      .computeAddressWithDeployer(saltHex, web3.utils.keccak256(constructorByteCode), this.factory.address);
-
-    await this.factory.deploy(deposit, saltHex, constructorByteCode);
-    expect(await balance.current(onChainComputed)).to.be.bignumber.equal(deposit);
-  });
-
-  it('should failed deploying a contract in an existent address', async function () {
-    await this.factory.deploy(0, saltHex, constructorByteCode, { from: deployerAccount });
-    await expectRevert(
-      this.factory.deploy(0, saltHex, constructorByteCode, { from: deployerAccount }), 'Create2: Failed on deploy'
-    );
-  });
-
-  it('should fail deploying a contract if the bytecode length is zero', async function () {
-    await expectRevert(
-      this.factory.deploy(0, saltHex, '0x', { from: deployerAccount }), 'Create2: bytecode length is zero'
-    );
+  describe('computeAddress', function () {
+    it('computes the correct contract address', async function () {
+      const onChainComputed = await this.factory
+        .computeAddress(saltHex, web3.utils.keccak256(constructorByteCode));
+      const offChainComputed =
+        computeCreate2Address(saltHex, constructorByteCode, this.factory.address);
+      expect(onChainComputed).to.equal(offChainComputed);
+    });
+
+    it('computes the correct contract address with deployer', async function () {
+      const onChainComputed = await this.factory
+        .computeAddressWithDeployer(saltHex, web3.utils.keccak256(constructorByteCode), deployerAccount);
+      const offChainComputed =
+        computeCreate2Address(saltHex, constructorByteCode, deployerAccount);
+      expect(onChainComputed).to.equal(offChainComputed);
+    });
   });
 
-  it('should fail deploying a contract if factory contract does not have sufficient balance', async function () {
-    await expectRevert(
-      this.factory.deploy(1, saltHex, constructorByteCode, { from: deployerAccount }),
-      'Create2: insufficient balance'
-    );
+  describe('deploy', function () {
+    it('deploys a ERC1820Implementer from inline assembly code', async function () {
+      const offChainComputed =
+        computeCreate2Address(saltHex, ERC1820Implementer.bytecode, this.factory.address);
+      await this.factory.deployERC1820Implementer(0, saltHex);
+      expect(ERC1820Implementer.bytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2));
+    });
+
+    it('deploys a ERC20Mock with correct balances', async function () {
+      const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address);
+
+      await this.factory.deploy(0, saltHex, constructorByteCode);
+
+      const erc20 = await ERC20Mock.at(offChainComputed);
+      expect(await erc20.balanceOf(deployerAccount)).to.be.bignumber.equal(new BN(100));
+    });
+
+    it('deploys a contract with funds deposited in the factory', async function () {
+      const deposit = ether('2');
+      await send.ether(deployerAccount, this.factory.address, deposit);
+      expect(await balance.current(this.factory.address)).to.be.bignumber.equal(deposit);
+
+      const onChainComputed = await this.factory
+        .computeAddressWithDeployer(saltHex, web3.utils.keccak256(constructorByteCode), this.factory.address);
+
+      await this.factory.deploy(deposit, saltHex, constructorByteCode);
+      expect(await balance.current(onChainComputed)).to.be.bignumber.equal(deposit);
+    });
+
+    it('fails deploying a contract in an existent address', async function () {
+      await this.factory.deploy(0, saltHex, constructorByteCode, { from: deployerAccount });
+      await expectRevert(
+        this.factory.deploy(0, saltHex, constructorByteCode, { from: deployerAccount }), 'Create2: Failed on deploy'
+      );
+    });
+
+    it('fails deploying a contract if the bytecode length is zero', async function () {
+      await expectRevert(
+        this.factory.deploy(0, saltHex, '0x', { from: deployerAccount }), 'Create2: bytecode length is zero'
+      );
+    });
+
+    it('fails deploying a contract if factory contract does not have sufficient balance', async function () {
+      await expectRevert(
+        this.factory.deploy(1, saltHex, constructorByteCode, { from: deployerAccount }),
+        'Create2: insufficient balance'
+      );
+    });
   });
 });
 

+ 61 - 57
test/utils/EnumerableMap.test.js

@@ -46,94 +46,98 @@ describe('EnumerableMap', function () {
     await expectMembersMatch(this.map, [], []);
   });
 
-  it('adds a key', async function () {
-    const receipt = await this.map.set(keyA, accountA);
-    expectEvent(receipt, 'OperationResult', { result: true });
+  describe('set', function () {
+    it('adds a key', async function () {
+      const receipt = await this.map.set(keyA, accountA);
+      expectEvent(receipt, 'OperationResult', { result: true });
 
-    await expectMembersMatch(this.map, [keyA], [accountA]);
-  });
+      await expectMembersMatch(this.map, [keyA], [accountA]);
+    });
 
-  it('adds several keys', async function () {
-    await this.map.set(keyA, accountA);
-    await this.map.set(keyB, accountB);
+    it('adds several keys', async function () {
+      await this.map.set(keyA, accountA);
+      await this.map.set(keyB, accountB);
 
-    await expectMembersMatch(this.map, [keyA, keyB], [accountA, accountB]);
-    expect(await this.map.contains(keyC)).to.equal(false);
-  });
+      await expectMembersMatch(this.map, [keyA, keyB], [accountA, accountB]);
+      expect(await this.map.contains(keyC)).to.equal(false);
+    });
 
-  it('returns false when adding keys already in the set', async function () {
-    await this.map.set(keyA, accountA);
+    it('returns false when adding keys already in the set', async function () {
+      await this.map.set(keyA, accountA);
 
-    const receipt = (await this.map.set(keyA, accountA));
-    expectEvent(receipt, 'OperationResult', { result: false });
+      const receipt = (await this.map.set(keyA, accountA));
+      expectEvent(receipt, 'OperationResult', { result: false });
 
-    await expectMembersMatch(this.map, [keyA], [accountA]);
-  });
+      await expectMembersMatch(this.map, [keyA], [accountA]);
+    });
 
-  it('updates values for keys already in the set', async function () {
-    await this.map.set(keyA, accountA);
+    it('updates values for keys already in the set', async function () {
+      await this.map.set(keyA, accountA);
 
-    await this.map.set(keyA, accountB);
+      await this.map.set(keyA, accountB);
 
-    await expectMembersMatch(this.map, [keyA], [accountB]);
+      await expectMembersMatch(this.map, [keyA], [accountB]);
+    });
   });
 
-  it('removes added keys', async function () {
-    await this.map.set(keyA, accountA);
+  describe('remove', function () {
+    it('removes added keys', async function () {
+      await this.map.set(keyA, accountA);
 
-    const receipt = await this.map.remove(keyA);
-    expectEvent(receipt, 'OperationResult', { result: true });
+      const receipt = await this.map.remove(keyA);
+      expectEvent(receipt, 'OperationResult', { result: true });
 
-    expect(await this.map.contains(keyA)).to.equal(false);
-    await expectMembersMatch(this.map, [], []);
-  });
+      expect(await this.map.contains(keyA)).to.equal(false);
+      await expectMembersMatch(this.map, [], []);
+    });
 
-  it('returns false when removing keys not in the set', async function () {
-    const receipt = await this.map.remove(keyA);
-    expectEvent(receipt, 'OperationResult', { result: false });
+    it('returns false when removing keys not in the set', async function () {
+      const receipt = await this.map.remove(keyA);
+      expectEvent(receipt, 'OperationResult', { result: false });
 
-    expect(await this.map.contains(keyA)).to.equal(false);
-  });
+      expect(await this.map.contains(keyA)).to.equal(false);
+    });
 
-  it('adds and removes multiple keys', async function () {
-    // []
+    it('adds and removes multiple keys', async function () {
+      // []
 
-    await this.map.set(keyA, accountA);
-    await this.map.set(keyC, accountC);
+      await this.map.set(keyA, accountA);
+      await this.map.set(keyC, accountC);
 
-    // [A, C]
+      // [A, C]
 
-    await this.map.remove(keyA);
-    await this.map.remove(keyB);
+      await this.map.remove(keyA);
+      await this.map.remove(keyB);
 
-    // [C]
+      // [C]
 
-    await this.map.set(keyB, accountB);
+      await this.map.set(keyB, accountB);
 
-    // [C, B]
+      // [C, B]
 
-    await this.map.set(keyA, accountA);
-    await this.map.remove(keyC);
+      await this.map.set(keyA, accountA);
+      await this.map.remove(keyC);
 
-    // [A, B]
+      // [A, B]
 
-    await this.map.set(keyA, accountA);
-    await this.map.set(keyB, accountB);
+      await this.map.set(keyA, accountA);
+      await this.map.set(keyB, accountB);
 
-    // [A, B]
+      // [A, B]
 
-    await this.map.set(keyC, accountC);
-    await this.map.remove(keyA);
+      await this.map.set(keyC, accountC);
+      await this.map.remove(keyA);
 
-    // [B, C]
+      // [B, C]
 
-    await this.map.set(keyA, accountA);
-    await this.map.remove(keyB);
+      await this.map.set(keyA, accountA);
+      await this.map.remove(keyB);
 
-    // [A, C]
+      // [A, C]
 
-    await expectMembersMatch(this.map, [keyA, keyC], [accountA, accountC]);
+      await expectMembersMatch(this.map, [keyA, keyC], [accountA, accountC]);
 
-    expect(await this.map.contains(keyB)).to.equal(false);
+      expect(await this.map.contains(keyB)).to.equal(false);
+    });
   });
 });

+ 60 - 54
test/utils/EnumerableSet.behavior.js

@@ -23,91 +23,97 @@ function shouldBehaveLikeSet (valueA, valueB, valueC) {
     await expectMembersMatch(this.set, []);
   });
 
-  it('adds a value', async function () {
-    const receipt = await this.set.add(valueA);
-    expectEvent(receipt, 'OperationResult', { result: true });
+  describe('add', function () {
+    it('adds a value', async function () {
+      const receipt = await this.set.add(valueA);
+      expectEvent(receipt, 'OperationResult', { result: true });
 
-    await expectMembersMatch(this.set, [valueA]);
-  });
+      await expectMembersMatch(this.set, [valueA]);
+    });
 
-  it('adds several values', async function () {
-    await this.set.add(valueA);
-    await this.set.add(valueB);
+    it('adds several values', async function () {
+      await this.set.add(valueA);
+      await this.set.add(valueB);
 
-    await expectMembersMatch(this.set, [valueA, valueB]);
-    expect(await this.set.contains(valueC)).to.equal(false);
-  });
+      await expectMembersMatch(this.set, [valueA, valueB]);
+      expect(await this.set.contains(valueC)).to.equal(false);
+    });
 
-  it('returns false when adding values already in the set', async function () {
-    await this.set.add(valueA);
+    it('returns false when adding values already in the set', async function () {
+      await this.set.add(valueA);
 
-    const receipt = (await this.set.add(valueA));
-    expectEvent(receipt, 'OperationResult', { result: false });
+      const receipt = (await this.set.add(valueA));
+      expectEvent(receipt, 'OperationResult', { result: false });
 
-    await expectMembersMatch(this.set, [valueA]);
+      await expectMembersMatch(this.set, [valueA]);
+    });
   });
 
-  it('reverts when retrieving non-existent elements', async function () {
-    await expectRevert(this.set.at(0), 'EnumerableSet: index out of bounds');
+  describe('at', function () {
+    it('reverts when retrieving non-existent elements', async function () {
+      await expectRevert(this.set.at(0), 'EnumerableSet: index out of bounds');
+    });
   });
 
-  it('removes added values', async function () {
-    await this.set.add(valueA);
+  describe('remove', function () {
+    it('removes added values', async function () {
+      await this.set.add(valueA);
 
-    const receipt = await this.set.remove(valueA);
-    expectEvent(receipt, 'OperationResult', { result: true });
+      const receipt = await this.set.remove(valueA);
+      expectEvent(receipt, 'OperationResult', { result: true });
 
-    expect(await this.set.contains(valueA)).to.equal(false);
-    await expectMembersMatch(this.set, []);
-  });
+      expect(await this.set.contains(valueA)).to.equal(false);
+      await expectMembersMatch(this.set, []);
+    });
 
-  it('returns false when removing values not in the set', async function () {
-    const receipt = await this.set.remove(valueA);
-    expectEvent(receipt, 'OperationResult', { result: false });
+    it('returns false when removing values not in the set', async function () {
+      const receipt = await this.set.remove(valueA);
+      expectEvent(receipt, 'OperationResult', { result: false });
 
-    expect(await this.set.contains(valueA)).to.equal(false);
-  });
+      expect(await this.set.contains(valueA)).to.equal(false);
+    });
 
-  it('adds and removes multiple values', async function () {
-    // []
+    it('adds and removes multiple values', async function () {
+      // []
 
-    await this.set.add(valueA);
-    await this.set.add(valueC);
+      await this.set.add(valueA);
+      await this.set.add(valueC);
 
-    // [A, C]
+      // [A, C]
 
-    await this.set.remove(valueA);
-    await this.set.remove(valueB);
+      await this.set.remove(valueA);
+      await this.set.remove(valueB);
 
-    // [C]
+      // [C]
 
-    await this.set.add(valueB);
+      await this.set.add(valueB);
 
-    // [C, B]
+      // [C, B]
 
-    await this.set.add(valueA);
-    await this.set.remove(valueC);
+      await this.set.add(valueA);
+      await this.set.remove(valueC);
 
-    // [A, B]
+      // [A, B]
 
-    await this.set.add(valueA);
-    await this.set.add(valueB);
+      await this.set.add(valueA);
+      await this.set.add(valueB);
 
-    // [A, B]
+      // [A, B]
 
-    await this.set.add(valueC);
-    await this.set.remove(valueA);
+      await this.set.add(valueC);
+      await this.set.remove(valueA);
 
-    // [B, C]
+      // [B, C]
 
-    await this.set.add(valueA);
-    await this.set.remove(valueB);
+      await this.set.add(valueA);
+      await this.set.remove(valueB);
 
-    // [A, C]
+      // [A, C]
 
-    await expectMembersMatch(this.set, [valueA, valueC]);
+      await expectMembersMatch(this.set, [valueA, valueC]);
 
-    expect(await this.set.contains(valueB)).to.equal(false);
+      expect(await this.set.contains(valueB)).to.equal(false);
+    });
   });
 }
 

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

@@ -12,7 +12,7 @@ describe('ReentrancyGuard', function () {
     expect(await this.reentrancyMock.counter()).to.be.bignumber.equal('0');
   });
 
-  it('should not allow remote callback', async function () {
+  it('does not allow remote callback', async function () {
     const attacker = await ReentrancyAttack.new();
     await expectRevert(
       this.reentrancyMock.countAndCall(attacker.address), 'ReentrancyAttack: failed call');
@@ -21,14 +21,13 @@ describe('ReentrancyGuard', function () {
   // The following are more side-effects than intended behavior:
   // I put them here as documentation, and to monitor any changes
   // in the side-effects.
-
-  it('should not allow local recursion', async function () {
+  it('does not allow local recursion', async function () {
     await expectRevert(
       this.reentrancyMock.countLocalRecursive(10), 'ReentrancyGuard: reentrant call'
     );
   });
 
-  it('should not allow indirect local recursion', async function () {
+  it('does not allow indirect local recursion', async function () {
     await expectRevert(
       this.reentrancyMock.countThisRecursive(10), 'ReentrancyMock: failed call'
     );

Некоторые файлы не были показаны из-за большого количества измененных файлов