Commit 22de765f by Francisco Giordano

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

parents 2acb1abb f2fb8cf2
...@@ -8,7 +8,7 @@ charset = utf-8 ...@@ -8,7 +8,7 @@ charset = utf-8
end_of_line = lf end_of_line = lf
indent_style = space indent_style = space
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = false
max_line_length = 120 max_line_length = 120
[*.sol] [*.sol]
...@@ -16,3 +16,6 @@ indent_size = 4 ...@@ -16,3 +16,6 @@ indent_size = 4
[*.js] [*.js]
indent_size = 2 indent_size = 2
[*.adoc]
max_line_length = 0
module.exports = {
timeout: 4000,
};
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
"mark-callable-contracts": "off", "mark-callable-contracts": "off",
"no-empty-blocks": "off", "no-empty-blocks": "off",
"compiler-version": ["error", "^0.7.0"], "compiler-version": ["error", "^0.7.0"],
"private-vars-leading-underscore": "error" "private-vars-leading-underscore": "error",
"reason-string": "off"
} }
} }
# Changelog # 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) ## 3.1.0 (2020-06-23)
### New features ### New features
......
# 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 {
```
...@@ -10,7 +10,7 @@ program that extracts the API Reference from source code. ...@@ -10,7 +10,7 @@ program that extracts the API Reference from source code.
The [`docs.openzeppelin.com`](https://github.com/OpenZeppelin/docs.openzeppelin.com) The [`docs.openzeppelin.com`](https://github.com/OpenZeppelin/docs.openzeppelin.com)
repository hosts the configuration for the entire site, which includes 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 To run the docs locally you should run `npm run docs:watch` on this
repository. repository.
...@@ -28,37 +28,78 @@ Consistency on the way classes are used is paramount to an easier understanding ...@@ -28,37 +28,78 @@ Consistency on the way classes are used is paramount to an easier understanding
#### D6 - Regular Audits #### 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. 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.
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 names should have a capital I prefix.
```
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.
# <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) [![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) [![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, ...@@ -44,7 +44,7 @@ To keep your system secure, you should **always** use the installed code as-is,
## Learn More ## 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. * [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). * [Tokens](https://docs.openzeppelin.com/contracts/tokens): create tradeable assets or collectives, and distribute them via [Crowdsales](https://docs.openzeppelin.com/contracts/crowdsales).
......
...@@ -115,7 +115,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -115,7 +115,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
/** /**
* @dev See `IRelayRecipient.preRelayedCall`. * @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: * * Requirements:
* *
...@@ -138,7 +138,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -138,7 +138,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
/** /**
* @dev See `IRelayRecipient.postRelayedCall`. * @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: * * Requirements:
* *
......
...@@ -41,7 +41,7 @@ interface IRelayHub { ...@@ -41,7 +41,7 @@ interface IRelayHub {
function registerRelay(uint256 transactionFee, string calldata url) external; 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. * {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); event RelayAdded(address indexed relay, address indexed owner, uint256 transactionFee, uint256 stake, uint256 unstakeDelay, string url);
...@@ -105,7 +105,7 @@ interface IRelayHub { ...@@ -105,7 +105,7 @@ interface IRelayHub {
event Deposited(address indexed recipient, address indexed from, uint256 amount); 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); function balanceOf(address target) external view returns (uint256);
......
...@@ -53,7 +53,7 @@ interface IRelayRecipient { ...@@ -53,7 +53,7 @@ interface IRelayRecipient {
* *
* Returns a value to be passed to {postRelayedCall}. * 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. * will not be executed, but the recipient will still be charged for the transaction's cost.
*/ */
function preRelayedCall(bytes calldata context) external returns (bytes32); function preRelayedCall(bytes calldata context) external returns (bytes32);
......
// 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;
}
}
// 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";
}
}
// 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");
}
}
// 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;
}
}
// 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;
}
}
// 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;
}
}
{ {
"name": "@openzeppelin/contracts", "name": "@openzeppelin/contracts",
"version": "3.1.0-solc-0.7", "version": "3.2.0-rc.0",
"description": "Secure Smart Contract library for Solidity", "description": "Secure Smart Contract library for Solidity",
"files": [ "files": [
"**/*.sol", "**/*.sol",
......
...@@ -62,7 +62,7 @@ contract ERC721PresetMinterPauserAutoId is Context, AccessControl, ERC721Burnabl ...@@ -62,7 +62,7 @@ contract ERC721PresetMinterPauserAutoId is Context, AccessControl, ERC721Burnabl
function mint(address to) public virtual { function mint(address to) public virtual {
require(hasRole(MINTER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have minter role to mint"); 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. // can be burned (destroyed), so we need a separate counter.
_mint(to, _tokenIdTracker.current()); _mint(to, _tokenIdTracker.current());
_tokenIdTracker.increment(); _tokenIdTracker.increment();
......
// 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;
}
}
// 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 {
}
}
// 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);
}
}
= 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}}
// 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();
}
}
// 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)
}
}
}
...@@ -28,7 +28,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { ...@@ -28,7 +28,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
// Mapping from account to operator approvals // Mapping from account to operator approvals
mapping (address => mapping(address => bool)) private _operatorApprovals; 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; string private _uri;
/* /*
...@@ -66,7 +66,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { ...@@ -66,7 +66,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
* @dev See {IERC1155MetadataURI-uri}. * @dev See {IERC1155MetadataURI-uri}.
* *
* This implementation returns the same URI for *all* token types. It relies * 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]. * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
* *
* Clients calling this function must replace the `\{id\}` substring with the * Clients calling this function must replace the `\{id\}` substring with the
...@@ -208,10 +208,10 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { ...@@ -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 * @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]. * 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 * URI or any of the amounts in the JSON file at said URI will be replaced by
* clients with the token type ID. * clients with the token type ID.
* *
......
...@@ -12,7 +12,7 @@ import "../../introspection/IERC165.sol"; ...@@ -12,7 +12,7 @@ import "../../introspection/IERC165.sol";
*/ */
interface IERC1155 is IERC165 { 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); event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
......
...@@ -258,9 +258,9 @@ contract ERC20 is Context, IERC20 { ...@@ -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. * e.g. set automatic allowances for certain subsystems, etc.
* *
* Emits an {Approval} event. * Emits an {Approval} event.
......
...@@ -104,28 +104,25 @@ abstract contract ERC20Snapshot is ERC20 { ...@@ -104,28 +104,25 @@ abstract contract ERC20Snapshot is ERC20 {
return snapshotted ? value : totalSupply(); 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 { if (from == address(0)) {
_updateAccountSnapshot(account); // mint
_updateAccountSnapshot(to);
_updateTotalSupplySnapshot(); _updateTotalSupplySnapshot();
} else if (to == address(0)) {
super._mint(account, value); // burn
} _updateAccountSnapshot(from);
function _burn(address account, uint256 value) internal virtual override {
_updateAccountSnapshot(account);
_updateTotalSupplySnapshot(); _updateTotalSupplySnapshot();
} else {
super._burn(account, value); // transfer
_updateAccountSnapshot(from);
_updateAccountSnapshot(to);
}
} }
function _valueAt(uint256 snapshotId, Snapshots storage snapshots) function _valueAt(uint256 snapshotId, Snapshots storage snapshots)
......
...@@ -5,7 +5,7 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/ ...@@ -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]. 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: There a few core contracts that implement the behavior specified in the EIP:
......
...@@ -254,7 +254,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable ...@@ -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`. * `_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. * 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: * Requirements:
* *
......
...@@ -9,7 +9,7 @@ import "../../introspection/IERC165.sol"; ...@@ -9,7 +9,7 @@ import "../../introspection/IERC165.sol";
*/ */
interface IERC721 is IERC165 { 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); event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
......
...@@ -5,7 +5,7 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/ ...@@ -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]. 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}. 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}.
......
...@@ -5,7 +5,7 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/ ...@@ -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). 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}. The token behavior itself is implemented in the core contracts: {IERC777}, {ERC777}.
......
...@@ -7,7 +7,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t ...@@ -7,7 +7,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
Security tools include: 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]. * {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. 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.
......
...@@ -131,7 +131,7 @@ By default, **accounts with a role cannot grant it or revoke it from other accou ...@@ -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. 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: Let's take a look at the ERC20 token example, this time taking advantage of the default admin role:
......
...@@ -94,9 +94,9 @@ The metadata uri can be obtained: ...@@ -94,9 +94,9 @@ The metadata uri can be obtained:
"https://game.example/api/item/{id}.json" "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: The JSON document for token ID 2 might look something like:
......
...@@ -52,7 +52,7 @@ The guides in the sidebar will teach about different concepts, and how to use th ...@@ -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: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. * 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. 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.
......
...@@ -69,12 +69,12 @@ While attempts will generally be made to lower the gas costs of working with Ope ...@@ -69,12 +69,12 @@ While attempts will generally be made to lower the gas costs of working with Ope
[[bugfixes]] [[bugfixes]]
=== Bug Fixes === 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]]
=== 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. 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.
......
<svg width="422" height="40" viewBox="0 0 422 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M35.7774 39.9836V29.6104H24.5468C21.4926 29.6104 18.6635 31.223 17.0993 33.8558L13.4584 39.9836H35.7774Z" fill="#63D2F9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.0332031 0.133484V10.5067H29.6139L35.7774 0.133484H0.0332031Z" fill="#4E5EE4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1902 16.3452L0.0657959 39.9836H12.0995L28.9898 11.7036H22.3447C19.0023 11.7036 15.9058 13.4661 14.1902 16.3452Z" fill="#63B0F9"/>
<path opacity="0.9" d="M288.7 30.0173C284.218 30.0173 281.078 26.6207 281.078 22.2251C281.078 17.8296 284.304 14.433 288.672 14.433C291.898 14.433 294.895 16.3739 295.695 19.6848H293.354C292.754 17.7439 290.87 16.488 288.729 16.488C285.56 16.488 283.419 18.9998 283.419 22.2251C283.419 25.5646 285.674 27.9622 288.786 27.9622C290.927 27.9622 292.84 26.7063 293.411 24.7654H295.723C294.952 28.0764 291.926 30.0173 288.7 30.0173ZM306.843 30.0173C302.39 30.0173 299.107 26.7349 299.107 22.2251C299.107 17.7439 302.447 14.433 306.872 14.433C311.354 14.433 314.608 17.7725 314.608 22.2251C314.608 26.6778 311.297 30.0173 306.843 30.0173ZM306.843 27.9622C310.041 27.9622 312.296 25.5361 312.296 22.2251C312.296 18.9427 310.041 16.488 306.843 16.488C303.646 16.488 301.419 18.9713 301.419 22.2251C301.419 25.5646 303.674 27.9622 306.843 27.9622ZM319.134 29.6462V14.804H321.389V16.9733H321.56C322.188 15.6318 323.787 14.433 326.099 14.433C329.554 14.433 331.638 17.0304 331.638 20.3128V29.6462H329.382V20.4555C329.382 18.0864 327.641 16.5451 325.557 16.5451C323.216 16.5451 321.389 18.3433 321.389 20.798V29.6462H319.134ZM342.815 29.6462C340.074 29.6462 338.39 28.162 338.39 25.165V16.7164H335.592V14.804H337.819C338.219 14.804 338.533 14.5471 338.533 14.0619V10.4941H340.645V14.804H345.213V16.7164H340.645V25.1365C340.645 26.7349 341.245 27.6768 342.957 27.6768H345.013V29.6462H342.815ZM349.738 29.6462V14.804H351.965V17.5727H352.136C352.564 16.3168 354.02 14.804 356.19 14.804H357.446V17.0589H356.219C353.507 17.0589 351.993 19.1711 351.993 21.9968V29.6462H349.738ZM367.367 30.0173C362.999 30.0173 359.973 26.792 359.973 22.2537C359.973 17.6868 362.999 14.433 367.196 14.433C370.308 14.433 371.963 16.431 372.42 17.2016H372.591V14.804H374.847V29.6462H372.677V27.3057H372.506C372.135 27.8481 370.593 30.0173 367.367 30.0173ZM367.51 27.9908C370.736 27.9908 372.677 25.3648 372.677 22.2537C372.677 18.6002 370.393 16.488 367.453 16.488C364.398 16.488 362.286 18.8856 362.286 22.2537C362.286 25.7359 364.398 27.9908 367.51 27.9908ZM386.709 30.0173C382.227 30.0173 379.087 26.6207 379.087 22.2251C379.087 17.8296 382.312 14.433 386.68 14.433C389.906 14.433 392.904 16.3739 393.703 19.6848H391.362C390.763 17.7439 388.879 16.488 386.737 16.488C383.569 16.488 381.427 18.9998 381.427 22.2251C381.427 25.5646 383.683 27.9622 386.795 27.9622C388.936 27.9622 390.848 26.7063 391.419 24.7654H393.732C392.961 28.0764 389.935 30.0173 386.709 30.0173ZM404.052 29.6462C401.312 29.6462 399.627 28.162 399.627 25.165V16.7164H396.83V14.804H399.056C399.456 14.804 399.77 14.5471 399.77 14.0619V10.4941H401.883V14.804H406.45V16.7164H401.883V25.1365C401.883 26.7349 402.482 27.6768 404.195 27.6768H406.25V29.6462H404.052ZM415.886 30.0173C412.375 30.0173 410.376 28.1049 410.119 25.3363H412.317C412.574 27.1059 413.831 28.0479 416 28.0479C418.227 28.0479 419.454 26.9632 419.454 25.4505C419.454 21.2832 410.576 24.4515 410.576 18.5717C410.576 16.0599 412.831 14.433 415.943 14.433C418.684 14.433 420.968 16.0314 421.196 18.7144H419.026C418.826 17.3158 417.77 16.3739 415.8 16.3739C413.945 16.3739 412.746 17.2872 412.746 18.5717C412.746 22.3393 421.653 19.1996 421.653 25.3363C421.653 28.1335 419.34 30.0173 415.886 30.0173Z" fill="#282846"/>
<path d="M61.3795 30.03C55.3284 30.03 50.8757 25.6059 50.8757 19.669C50.8757 13.7892 55.3569 9.30797 61.408 9.30797C67.4591 9.30797 71.9403 13.8463 71.9403 19.669C71.9403 25.5488 67.4305 30.03 61.3795 30.03ZM61.408 26.4051C65.2327 26.4051 67.9728 23.5508 67.9728 19.669C67.9728 15.8443 65.2327 12.9329 61.408 12.9329C57.5547 12.9329 54.8146 15.8443 54.8146 19.669C54.8146 23.5508 57.5547 26.4051 61.408 26.4051ZM75.9923 36.2238V14.6455H79.6457V16.5578H79.8455C80.2451 15.9299 81.6152 14.2744 84.3553 14.2744C88.5511 14.2744 91.4339 17.4141 91.4339 22.0951C91.4339 26.7761 88.5796 30.03 84.4695 30.03C81.815 30.03 80.3879 28.5458 79.9026 27.718H79.7028V36.2238H75.9923ZM83.6417 26.6619C86.0393 26.6619 87.6662 24.8067 87.6662 22.1522C87.6662 19.4121 86.0393 17.6424 83.6132 17.6424C81.1585 17.6424 79.6172 19.6119 79.6172 22.1522C79.6172 24.9494 81.3298 26.6619 83.6417 26.6619ZM102.051 30.03C97.6266 30.03 94.4869 26.719 94.4869 22.2378C94.4869 17.4997 97.6551 14.2744 102.051 14.2744C106.874 14.2744 109.5 17.8422 109.5 22.0666V23.2368H98.0833C98.1975 25.5202 99.7673 27.0615 102.165 27.0615C103.992 27.0615 105.39 26.2053 105.904 24.9779H109.358C108.616 28.032 105.933 30.03 102.051 30.03ZM98.1404 20.6109H105.961C105.79 18.5844 104.22 17.2428 102.051 17.2428C99.9671 17.2428 98.3687 18.7271 98.1404 20.6109ZM113.495 29.6589V14.6455H117.035V16.4722H117.234C117.834 15.359 119.318 14.2744 121.459 14.2744C124.827 14.2744 126.882 16.6434 126.882 19.8402V29.6589H123.171V20.8107C123.171 18.984 122.001 17.7566 120.288 17.7566C118.49 17.7566 117.206 19.1837 117.206 21.0676V29.6589H113.495ZM130.563 29.6589V26.2623L140.952 13.1612V12.9614H130.934V9.67903H145.69V13.0471L135.301 26.1482V26.3765H145.748V29.6589H130.563ZM155.794 30.03C151.369 30.03 148.23 26.719 148.23 22.2378C148.23 17.4997 151.398 14.2744 155.794 14.2744C160.617 14.2744 163.243 17.8422 163.243 22.0666V23.2368H151.826C151.94 25.5202 153.51 27.0615 155.908 27.0615C157.734 27.0615 159.133 26.2053 159.647 24.9779H163.1C162.358 28.032 159.675 30.03 155.794 30.03ZM151.883 20.6109H159.704C159.533 18.5844 157.963 17.2428 155.794 17.2428C153.71 17.2428 152.112 18.7271 151.883 20.6109ZM167.238 36.2238V14.6455H170.892V16.5578H171.091C171.491 15.9299 172.861 14.2744 175.601 14.2744C179.797 14.2744 182.68 17.4141 182.68 22.0951C182.68 26.7761 179.825 30.03 175.715 30.03C173.061 30.03 171.634 28.5458 171.148 27.718H170.949V36.2238H167.238ZM174.888 26.6619C177.285 26.6619 178.912 24.8067 178.912 22.1522C178.912 19.4121 177.285 17.6424 174.859 17.6424C172.404 17.6424 170.863 19.6119 170.863 22.1522C170.863 24.9494 172.576 26.6619 174.888 26.6619ZM186.675 36.2238V14.6455H190.328V16.5578H190.528C190.927 15.9299 192.298 14.2744 195.038 14.2744C199.233 14.2744 202.116 17.4141 202.116 22.0951C202.116 26.7761 199.262 30.03 195.152 30.03C192.497 30.03 191.07 28.5458 190.585 27.718H190.385V36.2238H186.675ZM194.324 26.6619C196.722 26.6619 198.349 24.8067 198.349 22.1522C198.349 19.4121 196.722 17.6424 194.296 17.6424C191.841 17.6424 190.3 19.6119 190.3 22.1522C190.3 24.9494 192.012 26.6619 194.324 26.6619ZM212.733 30.03C208.309 30.03 205.169 26.719 205.169 22.2378C205.169 17.4997 208.337 14.2744 212.733 14.2744C217.557 14.2744 220.183 17.8422 220.183 22.0666V23.2368H208.766C208.88 25.5202 210.45 27.0615 212.847 27.0615C214.674 27.0615 216.073 26.2053 216.586 24.9779H220.04C219.298 28.032 216.615 30.03 212.733 30.03ZM208.823 20.6109H216.643C216.472 18.5844 214.902 17.2428 212.733 17.2428C210.649 17.2428 209.051 18.7271 208.823 20.6109ZM227.831 29.6589C225.205 29.6589 223.721 28.0891 223.721 25.4346V8.25189H227.432V25.0921C227.432 25.9484 227.86 26.4051 228.63 26.4051H229.23V29.6589H227.831ZM234.652 12.6475C233.31 12.6475 232.368 11.8197 232.368 10.4782C232.368 9.1938 233.31 8.33752 234.652 8.33752C235.993 8.33752 236.935 9.1938 236.935 10.4782C236.935 11.8197 235.993 12.6475 234.652 12.6475ZM232.797 29.6589V14.6455H236.507V29.6589H232.797ZM241.016 29.6589V14.6455H244.555V16.4722H244.755C245.354 15.359 246.838 14.2744 248.979 14.2744C252.347 14.2744 254.402 16.6434 254.402 19.8402V29.6589H250.692V20.8107C250.692 18.984 249.521 17.7566 247.809 17.7566C246.011 17.7566 244.726 19.1837 244.726 21.0676V29.6589H241.016Z" fill="#282846"/>
<path opacity="0.9" d="M269.325 30.2504H267.125V9.62091H269.325V30.2504Z" fill="#282846"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="421.8" height="40" fill="white"/>
</clipPath>
</defs>
</svg>
{ {
"name": "openzeppelin-solidity", "name": "openzeppelin-solidity",
"version": "3.1.0-solc-0.7", "version": "3.2.0-rc.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
{ {
"name": "openzeppelin-solidity", "name": "openzeppelin-solidity",
"version": "3.1.0-solc-0.7", "version": "3.2.0-rc.0",
"description": "Secure Smart Contract library for Solidity", "description": "Secure Smart Contract library for Solidity",
"files": [ "files": [
"/contracts/**/*.sol", "/contracts/**/*.sol",
......
...@@ -13,10 +13,11 @@ describe('Ownable', function () { ...@@ -13,10 +13,11 @@ describe('Ownable', function () {
this.ownable = await Ownable.new({ from: owner }); 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); expect(await this.ownable.owner()).to.equal(owner);
}); });
describe('transfer ownership', function () {
it('changes owner after transfer', async function () { it('changes owner after transfer', async function () {
const receipt = await this.ownable.transferOwnership(other, { from: owner }); const receipt = await this.ownable.transferOwnership(other, { from: owner });
expectEvent(receipt, 'OwnershipTransferred'); expectEvent(receipt, 'OwnershipTransferred');
...@@ -24,20 +25,22 @@ describe('Ownable', function () { ...@@ -24,20 +25,22 @@ describe('Ownable', function () {
expect(await this.ownable.owner()).to.equal(other); expect(await this.ownable.owner()).to.equal(other);
}); });
it('should prevent non-owners from transferring', async function () { it('prevents non-owners from transferring', async function () {
await expectRevert( await expectRevert(
this.ownable.transferOwnership(other, { from: other }), this.ownable.transferOwnership(other, { from: other }),
'Ownable: caller is not the owner' 'Ownable: caller is not the owner'
); );
}); });
it('should guard ownership against stuck state', async function () { it('guards ownership against stuck state', async function () {
await expectRevert( await expectRevert(
this.ownable.transferOwnership(ZERO_ADDRESS, { from: owner }), this.ownable.transferOwnership(ZERO_ADDRESS, { from: owner }),
'Ownable: new owner is the zero address' 'Ownable: new owner is the zero address'
); );
}); });
});
describe('renounce ownership', function () {
it('loses owner after renouncement', async function () { it('loses owner after renouncement', async function () {
const receipt = await this.ownable.renounceOwnership({ from: owner }); const receipt = await this.ownable.renounceOwnership({ from: owner });
expectEvent(receipt, 'OwnershipTransferred'); expectEvent(receipt, 'OwnershipTransferred');
...@@ -45,10 +48,11 @@ describe('Ownable', function () { ...@@ -45,10 +48,11 @@ describe('Ownable', function () {
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 () { it('prevents non-owners from renouncement', async function () {
await expectRevert( await expectRevert(
this.ownable.renounceOwnership({ from: other }), this.ownable.renounceOwnership({ from: other }),
'Ownable: caller is not the owner' 'Ownable: caller is not the owner'
); );
}); });
});
}); });
...@@ -15,7 +15,7 @@ describe('MerkleProof', function () { ...@@ -15,7 +15,7 @@ describe('MerkleProof', function () {
}); });
describe('verify', 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 elements = ['a', 'b', 'c', 'd'];
const merkleTree = new MerkleTree(elements); const merkleTree = new MerkleTree(elements);
...@@ -28,7 +28,7 @@ describe('MerkleProof', function () { ...@@ -28,7 +28,7 @@ describe('MerkleProof', function () {
expect(await this.merkleProof.verify(proof, root, leaf)).to.equal(true); 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 correctElements = ['a', 'b', 'c'];
const correctMerkleTree = new MerkleTree(correctElements); const correctMerkleTree = new MerkleTree(correctElements);
...@@ -44,7 +44,7 @@ describe('MerkleProof', function () { ...@@ -44,7 +44,7 @@ describe('MerkleProof', function () {
expect(await this.merkleProof.verify(badProof, correctRoot, correctLeaf)).to.equal(false); 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 elements = ['a', 'b', 'c'];
const merkleTree = new MerkleTree(elements); const merkleTree = new MerkleTree(elements);
......
...@@ -38,7 +38,7 @@ const getSignFor = (contract, signer) => (redeemer, methodName, methodArgs = []) ...@@ -38,7 +38,7 @@ const getSignFor = (contract, signer) => (redeemer, methodName, methodArgs = [])
redeemer, 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 PADDED_SIGNATURE_SIZE = 2 * 96; // 96 bytes in hexadecimal string length
const DUMMY_SIGNATURE = `0x${web3.utils.padLeft('', REAL_SIGNATURE_SIZE)}`; const DUMMY_SIGNATURE = `0x${web3.utils.padLeft('', REAL_SIGNATURE_SIZE)}`;
......
...@@ -57,11 +57,11 @@ function shouldSupportInterfaces (interfaces = []) { ...@@ -57,11 +57,11 @@ function shouldSupportInterfaces (interfaces = []) {
const interfaceId = INTERFACE_IDS[k]; const interfaceId = INTERFACE_IDS[k];
describe(k, function () { describe(k, function () {
describe('ERC165\'s supportsInterface(bytes4)', 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); 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); expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true);
}); });
}); });
...@@ -69,7 +69,7 @@ function shouldSupportInterfaces (interfaces = []) { ...@@ -69,7 +69,7 @@ function shouldSupportInterfaces (interfaces = []) {
for (const fnName of INTERFACES[k]) { for (const fnName of INTERFACES[k]) {
const fnSig = FN_SIGNATURES[fnName]; const fnSig = FN_SIGNATURES[fnName];
describe(fnName, function () { 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); expect(this.contractUnderTest.abi.filter(fn => fn.signature === fnSig).length).to.equal(1);
}); });
}); });
......
...@@ -54,45 +54,48 @@ describe('PaymentSplitter', function () { ...@@ -54,45 +54,48 @@ describe('PaymentSplitter', function () {
this.contract = await PaymentSplitter.new(this.payees, this.shares); 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'); 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) => { await Promise.all(this.payees.map(async (payee, index) => {
expect(await this.contract.payee(index)).to.equal(payee); expect(await this.contract.payee(index)).to.equal(payee);
expect(await this.contract.released(payee)).to.be.bignumber.equal('0'); 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); await send.ether(owner, this.contract.address, amount);
expect(await balance.current(this.contract.address)).to.be.bignumber.equal(amount); expect(await balance.current(this.contract.address)).to.be.bignumber.equal(amount);
}); });
it('should store shares if address is payee', async function () { 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'); expect(await this.contract.shares(payee1)).to.be.bignumber.not.equal('0');
}); });
it('should not store shares if address is not payee', async function () { it('does not store shares if address is not payee', async function () {
expect(await this.contract.shares(nonpayee1)).to.be.bignumber.equal('0'); expect(await this.contract.shares(nonpayee1)).to.be.bignumber.equal('0');
}); });
});
it('should throw if no funds to claim', async function () { describe('release', async function () {
it('reverts if no funds to claim', async function () {
await expectRevert(this.contract.release(payee1), await expectRevert(this.contract.release(payee1),
'PaymentSplitter: account is not due payment' 'PaymentSplitter: account is not due payment'
); );
}); });
it('reverts if non-payee want to claim', async function () {
it('should throw if non-payee want to claim', async function () {
await send.ether(payer1, this.contract.address, amount); await send.ether(payer1, this.contract.address, amount);
await expectRevert(this.contract.release(nonpayee1), await expectRevert(this.contract.release(nonpayee1),
'PaymentSplitter: account has no shares' '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); await send.ether(payer1, this.contract.address, amount);
// receive funds // receive funds
......
...@@ -15,6 +15,7 @@ describe('PullPayment', function () { ...@@ -15,6 +15,7 @@ describe('PullPayment', function () {
this.contract = await PullPaymentMock.new({ value: amount }); this.contract = await PullPaymentMock.new({ value: amount });
}); });
describe('payments', function () {
it('can record an async payment correctly', async function () { it('can record an async payment correctly', async function () {
await this.contract.callTransfer(payee1, 100, { from: payer }); await this.contract.callTransfer(payee1, 100, { from: payer });
expect(await this.contract.payments(payee1)).to.be.bignumber.equal('100'); expect(await this.contract.payments(payee1)).to.be.bignumber.equal('100');
...@@ -34,7 +35,9 @@ describe('PullPayment', function () { ...@@ -34,7 +35,9 @@ describe('PullPayment', function () {
expect(await this.contract.payments(payee2)).to.be.bignumber.equal('300'); expect(await this.contract.payments(payee2)).to.be.bignumber.equal('300');
}); });
});
describe('withdrawPayments', function () {
it('can withdraw payment', async function () { it('can withdraw payment', async function () {
const balanceTracker = await balance.tracker(payee1); const balanceTracker = await balance.tracker(payee1);
...@@ -46,4 +49,5 @@ describe('PullPayment', function () { ...@@ -46,4 +49,5 @@ describe('PullPayment', function () {
expect(await balanceTracker.delta()).to.be.bignumber.equal(amount); expect(await balanceTracker.delta()).to.be.bignumber.equal(amount);
expect(await this.contract.payments(payee1)).to.be.bignumber.equal('0'); expect(await this.contract.payments(payee1)).to.be.bignumber.equal('0');
}); });
});
}); });
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);
});
});
});
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);
});
});
});
});
});
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);
});
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,
});
});
});
});
};
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);
});
...@@ -92,6 +92,14 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder, ...@@ -92,6 +92,14 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
), ),
'ERC1155: accounts and ids length mismatch' '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 () { it('reverts when one of the addresses is the zero address', async function () {
...@@ -143,6 +151,18 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder, ...@@ -143,6 +151,18 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
expect(result[1]).to.be.a.bignumber.equal(firstAmount); expect(result[1]).to.be.a.bignumber.equal(firstAmount);
expect(result[2]).to.be.a.bignumber.equal('0'); 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, ...@@ -298,8 +318,11 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
}); });
it('preserves operator\'s balances not involved in the transfer', async function () { it('preserves operator\'s balances not involved in the transfer', async function () {
const balance = await this.token.balanceOf(proxy, firstTokenId); const balance1 = await this.token.balanceOf(proxy, firstTokenId);
expect(balance).to.be.a.bignumber.equal('0'); 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, ...@@ -333,7 +356,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
value: firstAmount, value: firstAmount,
}); });
it('should call onERC1155Received', async function () { it('calls onERC1155Received', async function () {
await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'Received', { await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'Received', {
operator: multiTokenHolder, operator: multiTokenHolder,
from: multiTokenHolder, from: multiTokenHolder,
...@@ -366,7 +389,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder, ...@@ -366,7 +389,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
value: firstAmount, value: firstAmount,
}); });
it('should call onERC1155Received', async function () { it('calls onERC1155Received', async function () {
await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'Received', { await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'Received', {
operator: multiTokenHolder, operator: multiTokenHolder,
from: multiTokenHolder, from: multiTokenHolder,
...@@ -464,6 +487,16 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder, ...@@ -464,6 +487,16 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
), ),
'ERC1155: ids and amounts length mismatch' '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 () { it('reverts when transferring to zero address', async function () {
...@@ -599,7 +632,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder, ...@@ -599,7 +632,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
values: [firstAmount, secondAmount], values: [firstAmount, secondAmount],
}); });
it('should call onERC1155BatchReceived', async function () { it('calls onERC1155BatchReceived', async function () {
await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', { await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', {
operator: multiTokenHolder, operator: multiTokenHolder,
from: multiTokenHolder, from: multiTokenHolder,
...@@ -630,7 +663,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder, ...@@ -630,7 +663,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
values: [firstAmount, secondAmount], values: [firstAmount, secondAmount],
}); });
it('should call onERC1155Received', async function () { it('calls onERC1155Received', async function () {
await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', { await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', {
operator: multiTokenHolder, operator: multiTokenHolder,
from: multiTokenHolder, from: multiTokenHolder,
...@@ -684,6 +717,41 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder, ...@@ -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 () { context('to a contract that does not implement the required function', function () {
it('reverts', async function () { it('reverts', async function () {
const invalidReceiver = this.token; const invalidReceiver = this.token;
......
...@@ -28,7 +28,7 @@ describe('ERC1155', function () { ...@@ -28,7 +28,7 @@ describe('ERC1155', function () {
const mintAmounts = [new BN(5000), new BN(10000), new BN(42195)]; const mintAmounts = [new BN(5000), new BN(10000), new BN(42195)];
const burnAmounts = [new BN(5000), new BN(9001), new BN(195)]; const burnAmounts = [new BN(5000), new BN(9001), new BN(195)];
const data = '0xcafebabe'; const data = '0x12345678';
describe('_mint', function () { describe('_mint', function () {
it('reverts with a zero destination address', async function () { it('reverts with a zero destination address', async function () {
...@@ -72,6 +72,11 @@ describe('ERC1155', function () { ...@@ -72,6 +72,11 @@ describe('ERC1155', function () {
this.token.mintBatch(tokenBatchHolder, tokenBatchIds, mintAmounts.slice(1), data), this.token.mintBatch(tokenBatchHolder, tokenBatchIds, mintAmounts.slice(1), data),
'ERC1155: ids and amounts length mismatch' '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 () { context('with minted batch of tokens', function () {
...@@ -121,6 +126,21 @@ describe('ERC1155', 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 () { context('with minted-then-burnt tokens', function () {
beforeEach(async function () { beforeEach(async function () {
await this.token.mint(tokenHolder, tokenId, mintAmount, data); await this.token.mint(tokenHolder, tokenId, mintAmount, data);
...@@ -164,6 +184,11 @@ describe('ERC1155', function () { ...@@ -164,6 +184,11 @@ describe('ERC1155', function () {
this.token.burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts.slice(1)), this.token.burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts.slice(1)),
'ERC1155: ids and amounts length mismatch' '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 () { it('reverts when burning a non-existent token id', async function () {
......
...@@ -8,41 +8,52 @@ const { expect } = require('chai'); ...@@ -8,41 +8,52 @@ const { expect } = require('chai');
describe('ERC1155Holder', function () { describe('ERC1155Holder', function () {
const [creator] = accounts; const [creator] = accounts;
it('receives ERC1155 tokens', async function () {
const uri = 'https://token-cdn-domain/{id}.json'; 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 multiTokenIds = [new BN(1), new BN(2), new BN(3)];
const multiTokenAmounts = [new BN(1000), new BN(2000), new BN(3000)]; const multiTokenAmounts = [new BN(1000), new BN(2000), new BN(3000)];
await multiToken.mintBatch(creator, multiTokenIds, multiTokenAmounts, '0x', { from: creator }); const transferData = '0x12345678';
const transferData = '0xf00dbabe';
const holder = await ERC1155Holder.new(); 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 });
});
await multiToken.safeTransferFrom( it('receives ERC1155 tokens from a single ID', async function () {
await this.multiToken.safeTransferFrom(
creator, creator,
holder.address, this.holder.address,
multiTokenIds[0], multiTokenIds[0],
multiTokenAmounts[0], multiTokenAmounts[0],
transferData, transferData,
{ from: creator }, { 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]);
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));
}
});
await multiToken.safeBatchTransferFrom( 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, creator,
holder.address, this.holder.address,
multiTokenIds.slice(1), multiTokenIds,
multiTokenAmounts.slice(1), multiTokenAmounts,
transferData, transferData,
{ from: creator }, { from: creator },
); );
for (let i = 1; i < multiTokenIds.length; i++) { for (let i = 0; i < multiTokenIds.length; i++) {
expect(await multiToken.balanceOf(holder.address, multiTokenIds[i])).to.be.bignumber.equal(multiTokenAmounts[i]); expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[i]))
.to.be.bignumber.equal(multiTokenAmounts[i]);
} }
}); });
}); });
...@@ -134,7 +134,7 @@ describe('ERC20Snapshot', function () { ...@@ -134,7 +134,7 @@ describe('ERC20Snapshot', function () {
context('with balance changes after the snapshot', function () { context('with balance changes after the snapshot', function () {
beforeEach(async function () { beforeEach(async function () {
await this.token.transfer(recipient, new BN('10'), { from: initialHolder }); 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')); await this.token.burn(initialHolder, new BN('20'));
}); });
......
...@@ -6,21 +6,21 @@ function shouldBehaveLikeERC20Capped (minter, [other], cap) { ...@@ -6,21 +6,21 @@ function shouldBehaveLikeERC20Capped (minter, [other], cap) {
describe('capped token', function () { describe('capped token', function () {
const from = minter; 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); 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 }); await this.token.mint(other, cap.subn(1), { from });
expect(await this.token.totalSupply()).to.be.bignumber.equal(cap.subn(1)); 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 this.token.mint(other, cap.subn(1), { from });
await expectRevert(this.token.mint(other, 2, { from }), 'ERC20Capped: cap exceeded'); 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 this.token.mint(other, cap, { from });
await expectRevert(this.token.mint(other, 1, { from }), 'ERC20Capped: cap exceeded'); await expectRevert(this.token.mint(other, 1, { from }), 'ERC20Capped: cap exceeded');
}); });
......
...@@ -332,7 +332,7 @@ describe('ERC721', function () { ...@@ -332,7 +332,7 @@ describe('ERC721', function () {
shouldTransferTokensByUsers(transferFun); 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 }); const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: owner });
await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', { await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
...@@ -343,7 +343,7 @@ describe('ERC721', function () { ...@@ -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 }); const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: approved });
await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', { await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
...@@ -417,7 +417,7 @@ describe('ERC721', function () { ...@@ -417,7 +417,7 @@ describe('ERC721', function () {
const data = '0x42'; const data = '0x42';
describe('via safeMint', function () { // regular minting is tested in ERC721Mintable.test.js and others 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); this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, false);
const receipt = await this.token.safeMint(this.receiver.address, tokenId, data); const receipt = await this.token.safeMint(this.receiver.address, tokenId, data);
...@@ -428,7 +428,7 @@ describe('ERC721', function () { ...@@ -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); this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, false);
const receipt = await this.token.safeMint(this.receiver.address, tokenId); const receipt = await this.token.safeMint(this.receiver.address, tokenId);
...@@ -691,7 +691,7 @@ describe('ERC721', function () { ...@@ -691,7 +691,7 @@ describe('ERC721', function () {
await this.token.approve(approved, firstTokenId, { from: owner }); 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); expect(await this.token.getApproved(firstTokenId)).to.be.equal(approved);
}); });
}); });
...@@ -752,7 +752,7 @@ describe('ERC721', function () { ...@@ -752,7 +752,7 @@ describe('ERC721', function () {
}); });
describe('tokenByIndex', function () { describe('tokenByIndex', function () {
it('should return all tokens', async function () { it('returns all tokens', async function () {
const tokensListed = await Promise.all( const tokensListed = await Promise.all(
[0, 1].map(i => this.token.tokenByIndex(i)) [0, 1].map(i => this.token.tokenByIndex(i))
); );
...@@ -760,14 +760,14 @@ describe('ERC721', function () { ...@@ -760,14 +760,14 @@ describe('ERC721', function () {
secondTokenId.toNumber()]); 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( await expectRevert(
this.token.tokenByIndex(2), 'EnumerableMap: index out of bounds' this.token.tokenByIndex(2), 'EnumerableMap: index out of bounds'
); );
}); });
[firstTokenId, secondTokenId].forEach(function (tokenId) { [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 newTokenId = new BN(300);
const anotherNewTokenId = new BN(400); const anotherNewTokenId = new BN(400);
......
...@@ -86,7 +86,7 @@ describe('ERC721Pausable', function () { ...@@ -86,7 +86,7 @@ describe('ERC721Pausable', function () {
}); });
describe('exists', 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); expect(await this.token.exists(firstTokenId)).to.equal(true);
}); });
}); });
......
...@@ -15,11 +15,11 @@ describe('Address', function () { ...@@ -15,11 +15,11 @@ describe('Address', function () {
}); });
describe('isContract', 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); 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(); const contract = await AddressImpl.new();
expect(await this.mock.isContract(contract.address)).to.equal(true); expect(await this.mock.isContract(contract.address)).to.equal(true);
}); });
......
...@@ -6,6 +6,7 @@ const { expect } = require('chai'); ...@@ -6,6 +6,7 @@ const { expect } = require('chai');
const ArraysImpl = contract.fromArtifact('ArraysImpl'); const ArraysImpl = contract.fromArtifact('ArraysImpl');
describe('Arrays', function () { describe('Arrays', function () {
describe('findUpperBound', function () {
context('Even number of elements', function () { context('Even number of elements', function () {
const EVEN_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; const EVEN_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
...@@ -13,23 +14,23 @@ describe('Arrays', function () { ...@@ -13,23 +14,23 @@ describe('Arrays', function () {
this.arrays = await ArraysImpl.new(EVEN_ELEMENTS_ARRAY); this.arrays = await ArraysImpl.new(EVEN_ELEMENTS_ARRAY);
}); });
it('should return correct index for the basic case', async function () { it('returns correct index for the basic case', async function () {
expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5'); expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5');
}); });
it('should return 0 for the first element', async function () { it('returns 0 for the first element', async function () {
expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0'); expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0');
}); });
it('should return index of the last element', async function () { it('returns index of the last element', async function () {
expect(await this.arrays.findUpperBound(20)).to.be.bignumber.equal('9'); 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 () { 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'); expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('10');
}); });
it('should return 0 for the element under the lower boundary', async function () { it('returns 0 for the element under the lower boundary', async function () {
expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0'); expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0');
}); });
}); });
...@@ -41,23 +42,23 @@ describe('Arrays', function () { ...@@ -41,23 +42,23 @@ describe('Arrays', function () {
this.arrays = await ArraysImpl.new(ODD_ELEMENTS_ARRAY); this.arrays = await ArraysImpl.new(ODD_ELEMENTS_ARRAY);
}); });
it('should return correct index for the basic case', async function () { it('returns correct index for the basic case', async function () {
expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5'); expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5');
}); });
it('should return 0 for the first element', async function () { it('returns 0 for the first element', async function () {
expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0'); expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0');
}); });
it('should return index of the last element', async function () { it('returns index of the last element', async function () {
expect(await this.arrays.findUpperBound(21)).to.be.bignumber.equal('10'); 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 () { 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'); expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('11');
}); });
it('should return 0 for the element under the lower boundary', async function () { it('returns 0 for the element under the lower boundary', async function () {
expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0'); expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0');
}); });
}); });
...@@ -69,7 +70,7 @@ describe('Arrays', function () { ...@@ -69,7 +70,7 @@ describe('Arrays', function () {
this.arrays = await ArraysImpl.new(WITH_GAP_ARRAY); this.arrays = await ArraysImpl.new(WITH_GAP_ARRAY);
}); });
it('should return index of first element in next filled range', async function () { it('returns index of first element in next filled range', async function () {
expect(await this.arrays.findUpperBound(17)).to.be.bignumber.equal('5'); expect(await this.arrays.findUpperBound(17)).to.be.bignumber.equal('5');
}); });
}); });
...@@ -79,8 +80,9 @@ describe('Arrays', function () { ...@@ -79,8 +80,9 @@ describe('Arrays', function () {
this.arrays = await ArraysImpl.new([]); this.arrays = await ArraysImpl.new([]);
}); });
it('should always return 0 for empty array', async function () { it('always returns 0 for empty array', async function () {
expect(await this.arrays.findUpperBound(10)).to.be.bignumber.equal('0'); expect(await this.arrays.findUpperBound(10)).to.be.bignumber.equal('0');
}); });
}); });
});
}); });
...@@ -15,6 +15,7 @@ describe('Counters', function () { ...@@ -15,6 +15,7 @@ describe('Counters', function () {
}); });
describe('increment', function () { describe('increment', function () {
context('starting from 0', function () {
it('increments the current value by one', async function () { it('increments the current value by one', async function () {
await this.counter.increment(); await this.counter.increment();
expect(await this.counter.current()).to.be.bignumber.equal('1'); expect(await this.counter.current()).to.be.bignumber.equal('1');
...@@ -28,13 +29,14 @@ describe('Counters', function () { ...@@ -28,13 +29,14 @@ describe('Counters', function () {
expect(await this.counter.current()).to.be.bignumber.equal('3'); expect(await this.counter.current()).to.be.bignumber.equal('3');
}); });
}); });
});
describe('decrement', function () { describe('decrement', function () {
beforeEach(async function () { beforeEach(async function () {
await this.counter.increment(); await this.counter.increment();
expect(await this.counter.current()).to.be.bignumber.equal('1'); expect(await this.counter.current()).to.be.bignumber.equal('1');
}); });
context('starting from 1', function () {
it('decrements the current value by one', async function () { it('decrements the current value by one', async function () {
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');
...@@ -44,7 +46,8 @@ describe('Counters', function () { ...@@ -44,7 +46,8 @@ describe('Counters', function () {
await this.counter.decrement(); await this.counter.decrement();
await expectRevert(this.counter.decrement(), 'SafeMath: subtraction overflow'); await expectRevert(this.counter.decrement(), 'SafeMath: subtraction overflow');
}); });
});
context('after incremented to 3', function () {
it('can be called multiple times', async function () { it('can be called multiple times', async function () {
await this.counter.increment(); await this.counter.increment();
await this.counter.increment(); await this.counter.increment();
...@@ -58,4 +61,5 @@ describe('Counters', function () { ...@@ -58,4 +61,5 @@ describe('Counters', function () {
expect(await this.counter.current()).to.be.bignumber.equal('0'); expect(await this.counter.current()).to.be.bignumber.equal('0');
}); });
}); });
});
}); });
...@@ -23,8 +23,8 @@ describe('Create2', function () { ...@@ -23,8 +23,8 @@ describe('Create2', function () {
beforeEach(async function () { beforeEach(async function () {
this.factory = await Create2Impl.new(); this.factory = await Create2Impl.new();
}); });
describe('computeAddress', function () {
it('should compute the correct contract address', async function () { it('computes the correct contract address', async function () {
const onChainComputed = await this.factory const onChainComputed = await this.factory
.computeAddress(saltHex, web3.utils.keccak256(constructorByteCode)); .computeAddress(saltHex, web3.utils.keccak256(constructorByteCode));
const offChainComputed = const offChainComputed =
...@@ -32,24 +32,24 @@ describe('Create2', function () { ...@@ -32,24 +32,24 @@ describe('Create2', function () {
expect(onChainComputed).to.equal(offChainComputed); expect(onChainComputed).to.equal(offChainComputed);
}); });
it('should compute the correct contract address with deployer', async function () { it('computes the correct contract address with deployer', async function () {
const onChainComputed = await this.factory const onChainComputed = await this.factory
.computeAddressWithDeployer(saltHex, web3.utils.keccak256(constructorByteCode), deployerAccount); .computeAddressWithDeployer(saltHex, web3.utils.keccak256(constructorByteCode), deployerAccount);
const offChainComputed = const offChainComputed =
computeCreate2Address(saltHex, constructorByteCode, deployerAccount); computeCreate2Address(saltHex, constructorByteCode, deployerAccount);
expect(onChainComputed).to.equal(offChainComputed); expect(onChainComputed).to.equal(offChainComputed);
}); });
});
it('should deploy a ERC1820Implementer from inline assembly code', async function () { describe('deploy', function () {
it('deploys a ERC1820Implementer from inline assembly code', async function () {
const offChainComputed = const offChainComputed =
computeCreate2Address(saltHex, ERC1820Implementer.bytecode, this.factory.address); computeCreate2Address(saltHex, ERC1820Implementer.bytecode, this.factory.address);
await this.factory.deployERC1820Implementer(0, saltHex); await this.factory.deployERC1820Implementer(0, saltHex);
expect(ERC1820Implementer.bytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2)); expect(ERC1820Implementer.bytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2));
}); });
it('should deploy a ERC20Mock with correct balances', async function () { it('deploys a ERC20Mock with correct balances', async function () {
const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address); const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address);
await this.factory.deploy(0, saltHex, constructorByteCode); await this.factory.deploy(0, saltHex, constructorByteCode);
...@@ -58,7 +58,7 @@ describe('Create2', function () { ...@@ -58,7 +58,7 @@ describe('Create2', function () {
expect(await erc20.balanceOf(deployerAccount)).to.be.bignumber.equal(new BN(100)); 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 () { it('deploys a contract with funds deposited in the factory', async function () {
const deposit = ether('2'); const deposit = ether('2');
await send.ether(deployerAccount, this.factory.address, deposit); await send.ether(deployerAccount, this.factory.address, deposit);
expect(await balance.current(this.factory.address)).to.be.bignumber.equal(deposit); expect(await balance.current(this.factory.address)).to.be.bignumber.equal(deposit);
...@@ -70,25 +70,26 @@ describe('Create2', function () { ...@@ -70,25 +70,26 @@ describe('Create2', function () {
expect(await balance.current(onChainComputed)).to.be.bignumber.equal(deposit); expect(await balance.current(onChainComputed)).to.be.bignumber.equal(deposit);
}); });
it('should failed deploying a contract in an existent address', async function () { it('fails deploying a contract in an existent address', async function () {
await this.factory.deploy(0, saltHex, constructorByteCode, { from: deployerAccount }); await this.factory.deploy(0, saltHex, constructorByteCode, { from: deployerAccount });
await expectRevert( await expectRevert(
this.factory.deploy(0, saltHex, constructorByteCode, { from: deployerAccount }), 'Create2: Failed on deploy' 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 () { it('fails deploying a contract if the bytecode length is zero', async function () {
await expectRevert( await expectRevert(
this.factory.deploy(0, saltHex, '0x', { from: deployerAccount }), 'Create2: bytecode length is zero' this.factory.deploy(0, saltHex, '0x', { from: deployerAccount }), 'Create2: bytecode length is zero'
); );
}); });
it('should fail deploying a contract if factory contract does not have sufficient balance', async function () { it('fails deploying a contract if factory contract does not have sufficient balance', async function () {
await expectRevert( await expectRevert(
this.factory.deploy(1, saltHex, constructorByteCode, { from: deployerAccount }), this.factory.deploy(1, saltHex, constructorByteCode, { from: deployerAccount }),
'Create2: insufficient balance' 'Create2: insufficient balance'
); );
}); });
});
}); });
function computeCreate2Address (saltHex, bytecode, deployer) { function computeCreate2Address (saltHex, bytecode, deployer) {
......
...@@ -46,6 +46,7 @@ describe('EnumerableMap', function () { ...@@ -46,6 +46,7 @@ describe('EnumerableMap', function () {
await expectMembersMatch(this.map, [], []); await expectMembersMatch(this.map, [], []);
}); });
describe('set', function () {
it('adds a key', async function () { it('adds a key', async function () {
const receipt = await this.map.set(keyA, accountA); const receipt = await this.map.set(keyA, accountA);
expectEvent(receipt, 'OperationResult', { result: true }); expectEvent(receipt, 'OperationResult', { result: true });
...@@ -77,7 +78,9 @@ describe('EnumerableMap', function () { ...@@ -77,7 +78,9 @@ describe('EnumerableMap', function () {
await expectMembersMatch(this.map, [keyA], [accountB]); await expectMembersMatch(this.map, [keyA], [accountB]);
}); });
});
describe('remove', function () {
it('removes added keys', async function () { it('removes added keys', async function () {
await this.map.set(keyA, accountA); await this.map.set(keyA, accountA);
...@@ -136,4 +139,5 @@ describe('EnumerableMap', function () { ...@@ -136,4 +139,5 @@ describe('EnumerableMap', function () {
expect(await this.map.contains(keyB)).to.equal(false); expect(await this.map.contains(keyB)).to.equal(false);
}); });
});
}); });
...@@ -23,6 +23,7 @@ function shouldBehaveLikeSet (valueA, valueB, valueC) { ...@@ -23,6 +23,7 @@ function shouldBehaveLikeSet (valueA, valueB, valueC) {
await expectMembersMatch(this.set, []); await expectMembersMatch(this.set, []);
}); });
describe('add', function () {
it('adds a value', async function () { it('adds a value', async function () {
const receipt = await this.set.add(valueA); const receipt = await this.set.add(valueA);
expectEvent(receipt, 'OperationResult', { result: true }); expectEvent(receipt, 'OperationResult', { result: true });
...@@ -46,11 +47,15 @@ function shouldBehaveLikeSet (valueA, valueB, valueC) { ...@@ -46,11 +47,15 @@ function shouldBehaveLikeSet (valueA, valueB, valueC) {
await expectMembersMatch(this.set, [valueA]); await expectMembersMatch(this.set, [valueA]);
}); });
});
describe('at', function () {
it('reverts when retrieving non-existent elements', async function () { it('reverts when retrieving non-existent elements', async function () {
await expectRevert(this.set.at(0), 'EnumerableSet: index out of bounds'); await expectRevert(this.set.at(0), 'EnumerableSet: index out of bounds');
}); });
});
describe('remove', function () {
it('removes added values', async function () { it('removes added values', async function () {
await this.set.add(valueA); await this.set.add(valueA);
...@@ -109,6 +114,7 @@ function shouldBehaveLikeSet (valueA, valueB, valueC) { ...@@ -109,6 +114,7 @@ function shouldBehaveLikeSet (valueA, valueB, valueC) {
expect(await this.set.contains(valueB)).to.equal(false); expect(await this.set.contains(valueB)).to.equal(false);
}); });
});
} }
module.exports = { module.exports = {
......
...@@ -12,7 +12,7 @@ describe('ReentrancyGuard', function () { ...@@ -12,7 +12,7 @@ describe('ReentrancyGuard', function () {
expect(await this.reentrancyMock.counter()).to.be.bignumber.equal('0'); 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(); const attacker = await ReentrancyAttack.new();
await expectRevert( await expectRevert(
this.reentrancyMock.countAndCall(attacker.address), 'ReentrancyAttack: failed call'); this.reentrancyMock.countAndCall(attacker.address), 'ReentrancyAttack: failed call');
...@@ -21,14 +21,13 @@ describe('ReentrancyGuard', function () { ...@@ -21,14 +21,13 @@ describe('ReentrancyGuard', function () {
// The following are more side-effects than intended behavior: // The following are more side-effects than intended behavior:
// I put them here as documentation, and to monitor any changes // I put them here as documentation, and to monitor any changes
// in the side-effects. // in the side-effects.
it('does not allow local recursion', async function () {
it('should not allow local recursion', async function () {
await expectRevert( await expectRevert(
this.reentrancyMock.countLocalRecursive(10), 'ReentrancyGuard: reentrant call' 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( await expectRevert(
this.reentrancyMock.countThisRecursive(10), 'ReentrancyMock: failed call' this.reentrancyMock.countThisRecursive(10), 'ReentrancyMock: failed call'
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment