Commit 92a60b25 by Nicolás Venturo

Merge branch 'master' into release-v3.0.0

parents 60a73c63 a0f6bd39
contact_links:
- name: Support request
url: https://forum.openzeppelin.com/c/support/contracts/18
about: Ask the community in the Community Forum
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
"func-order": "off", "func-order": "off",
"mark-callable-contracts": "off", "mark-callable-contracts": "off",
"no-empty-blocks": "off", "no-empty-blocks": "off",
"compiler-version": ["error", "^0.6.0"] "compiler-version": ["error", "^0.6.0"],
"private-vars-leading-underscore": "error"
} }
} }
...@@ -4,13 +4,18 @@ ...@@ -4,13 +4,18 @@
### New features ### New features
* `AccessControl`: new contract for managing permissions in a system, replacement for `Ownable` and `Roles`. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112)) * `AccessControl`: new contract for managing permissions in a system, replacement for `Ownable` and `Roles`. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112))
* `SafeCast`: new functions to convert to and from signed and unsigned values: `toUint256` and `toInt256`. ([#2123](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2123))
* `EnumerableMap`: a new data structure for key-value pairs (like `mapping`) that can be iterated over. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160))
### Breaking changes ### Breaking changes
* `ERC721`: `burn(owner, tokenId)` was removed, use `burn(owner)` instead. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) * `ERC721`: `burn(owner, tokenId)` was removed, use `burn(tokenId)` instead. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125))
* `ERC721`: `_checkOnERC721Received` was removed. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) * `ERC721`: `_checkOnERC721Received` was removed. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125))
* `ERC721`: `_transferFrom` and `_safeTransferFrom` were renamed to `_transfer` and `_safeTransfer`. ([#2162](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2162))
* `Ownable`: removed `_transferOwnership`. ([#2162](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2162))
* `PullPayment`, `Escrow`: `withdrawWithGas` was removed. The old `withdraw` function now forwards all gas. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) * `PullPayment`, `Escrow`: `withdrawWithGas` was removed. The old `withdraw` function now forwards all gas. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125))
* `Roles` was removed, use `AccessControl` as a replacement. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112)) * `Roles` was removed, use `AccessControl` as a replacement. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112))
* `ECDSA`: when receiving an invalid signature, `recover` now reverts instead of returning the zero address. ([#2114](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2114)) * `ECDSA`: when receiving an invalid signature, `recover` now reverts instead of returning the zero address. ([#2114](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2114))
* `Create2`: added an `amount` argument to `deploy` for contracts with `payable` constructors. ([#2117](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2117))
* `Pausable`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) * `Pausable`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122))
* `Strings`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) * `Strings`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122))
* `Counters`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) * `Counters`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122))
...@@ -24,6 +29,12 @@ ...@@ -24,6 +29,12 @@
* `Address`: removed `toPayable`, use `payable(address)` instead. ([#2133](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2133)) * `Address`: removed `toPayable`, use `payable(address)` instead. ([#2133](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2133))
* `ERC777`: `_send`, `_mint` and `_burn` now use the caller as the operator. ([#2134](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2134)) * `ERC777`: `_send`, `_mint` and `_burn` now use the caller as the operator. ([#2134](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2134))
* `ERC777`: removed `_callsTokensToSend` and `_callTokensReceived`. ([#2134](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2134)) * `ERC777`: removed `_callsTokensToSend` and `_callTokensReceived`. ([#2134](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2134))
* `EnumerableSet`: renamed `get` to `at`. ([#2151](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2151))
* `ERC165Checker`: functions no longer have a leading underscore. ([#2150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2150))
* `ERC721Metadata`, `ERC721Enumerable`: these contracts were removed, and their functionality merged into `ERC721`. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160))
* `ERC721`: added a constructor for `name` and `symbol`. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160))
* `ERC20Detailed`: this contract was removed and its functionality merged into `ERC20`. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161))
* `ERC20`: added a constructor for `name` and `symbol`. `decimals` now defaults to 18. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161))
## 2.5.0 (2020-02-04) ## 2.5.0 (2020-02-04)
......
...@@ -19,11 +19,11 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -19,11 +19,11 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
// Default RelayHub address, deployed on mainnet and all testnets at the same address // Default RelayHub address, deployed on mainnet and all testnets at the same address
address private _relayHub = 0xD216153c06E857cD7f72665E0aF1d7D82172F494; address private _relayHub = 0xD216153c06E857cD7f72665E0aF1d7D82172F494;
uint256 constant private RELAYED_CALL_ACCEPTED = 0; uint256 constant private _RELAYED_CALL_ACCEPTED = 0;
uint256 constant private RELAYED_CALL_REJECTED = 11; uint256 constant private _RELAYED_CALL_REJECTED = 11;
// How much gas is forwarded to postRelayedCall // How much gas is forwarded to postRelayedCall
uint256 constant internal POST_RELAYED_CALL_MAX_GAS = 100000; uint256 constant internal _POST_RELAYED_CALL_MAX_GAS = 100000;
/** /**
* @dev Emitted when a contract changes its {IRelayHub} contract to a new one. * @dev Emitted when a contract changes its {IRelayHub} contract to a new one.
...@@ -119,7 +119,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -119,7 +119,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
* *
* - the caller must be the `RelayHub` contract. * - the caller must be the `RelayHub` contract.
*/ */
function preRelayedCall(bytes calldata context) external virtual override returns (bytes32) { function preRelayedCall(bytes memory context) public virtual override returns (bytes32) {
require(msg.sender == getHubAddr(), "GSNRecipient: caller is not RelayHub"); require(msg.sender == getHubAddr(), "GSNRecipient: caller is not RelayHub");
return _preRelayedCall(context); return _preRelayedCall(context);
} }
...@@ -142,7 +142,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -142,7 +142,7 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
* *
* - the caller must be the `RelayHub` contract. * - the caller must be the `RelayHub` contract.
*/ */
function postRelayedCall(bytes calldata context, bool success, uint256 actualCharge, bytes32 preRetVal) external virtual override { function postRelayedCall(bytes memory context, bool success, uint256 actualCharge, bytes32 preRetVal) public virtual override {
require(msg.sender == getHubAddr(), "GSNRecipient: caller is not RelayHub"); require(msg.sender == getHubAddr(), "GSNRecipient: caller is not RelayHub");
_postRelayedCall(context, success, actualCharge, preRetVal); _postRelayedCall(context, success, actualCharge, preRetVal);
} }
...@@ -170,14 +170,14 @@ abstract contract GSNRecipient is IRelayRecipient, Context { ...@@ -170,14 +170,14 @@ abstract contract GSNRecipient is IRelayRecipient, Context {
* This overload forwards `context` to _preRelayedCall and _postRelayedCall. * This overload forwards `context` to _preRelayedCall and _postRelayedCall.
*/ */
function _approveRelayedCall(bytes memory context) internal pure returns (uint256, bytes memory) { function _approveRelayedCall(bytes memory context) internal pure returns (uint256, bytes memory) {
return (RELAYED_CALL_ACCEPTED, context); return (_RELAYED_CALL_ACCEPTED, context);
} }
/** /**
* @dev Return this in acceptRelayedCall to impede execution of a relayed call. No fees will be charged. * @dev Return this in acceptRelayedCall to impede execution of a relayed call. No fees will be charged.
*/ */
function _rejectRelayedCall(uint256 errorCode) internal pure returns (uint256, bytes memory) { function _rejectRelayedCall(uint256 errorCode) internal pure returns (uint256, bytes memory) {
return (RELAYED_CALL_REJECTED + errorCode, ""); return (_RELAYED_CALL_REJECTED + errorCode, "");
} }
/* /*
......
...@@ -5,7 +5,6 @@ import "../math/SafeMath.sol"; ...@@ -5,7 +5,6 @@ import "../math/SafeMath.sol";
import "../access/Ownable.sol"; import "../access/Ownable.sol";
import "../token/ERC20/SafeERC20.sol"; import "../token/ERC20/SafeERC20.sol";
import "../token/ERC20/ERC20.sol"; import "../token/ERC20/ERC20.sol";
import "../token/ERC20/ERC20Detailed.sol";
/** /**
* @dev A xref:ROOT:gsn-strategies.adoc#gsn-strategies[GSN strategy] that charges transaction fees in a special purpose ERC20 * @dev A xref:ROOT:gsn-strategies.adoc#gsn-strategies[GSN strategy] that charges transaction fees in a special purpose ERC20
...@@ -30,7 +29,7 @@ contract GSNRecipientERC20Fee is GSNRecipient { ...@@ -30,7 +29,7 @@ contract GSNRecipientERC20Fee is GSNRecipient {
* @dev The arguments to the constructor are the details that the gas payment token will have: `name` and `symbol`. `decimals` is hard-coded to 18. * @dev The arguments to the constructor are the details that the gas payment token will have: `name` and `symbol`. `decimals` is hard-coded to 18.
*/ */
constructor(string memory name, string memory symbol) public { constructor(string memory name, string memory symbol) public {
_token = new __unstable__ERC20Owned(name, symbol, 18); _token = new __unstable__ERC20Owned(name, symbol);
} }
/** /**
...@@ -53,15 +52,15 @@ contract GSNRecipientERC20Fee is GSNRecipient { ...@@ -53,15 +52,15 @@ contract GSNRecipientERC20Fee is GSNRecipient {
function acceptRelayedCall( function acceptRelayedCall(
address, address,
address from, address from,
bytes calldata, bytes memory,
uint256 transactionFee, uint256 transactionFee,
uint256 gasPrice, uint256 gasPrice,
uint256, uint256,
uint256, uint256,
bytes calldata, bytes memory,
uint256 maxPossibleCharge uint256 maxPossibleCharge
) )
external public
view view
virtual virtual
override override
...@@ -97,7 +96,7 @@ contract GSNRecipientERC20Fee is GSNRecipient { ...@@ -97,7 +96,7 @@ contract GSNRecipientERC20Fee is GSNRecipient {
// actualCharge is an _estimated_ charge, which assumes postRelayedCall will use all available gas. // actualCharge is an _estimated_ charge, which assumes postRelayedCall will use all available gas.
// This implementation's gas cost can be roughly estimated as 10k gas, for the two SSTORE operations in an // This implementation's gas cost can be roughly estimated as 10k gas, for the two SSTORE operations in an
// ERC20 transfer. // ERC20 transfer.
uint256 overestimation = _computeCharge(POST_RELAYED_CALL_MAX_GAS.sub(10000), gasPrice, transactionFee); uint256 overestimation = _computeCharge(_POST_RELAYED_CALL_MAX_GAS.sub(10000), gasPrice, transactionFee);
actualCharge = actualCharge.sub(overestimation); actualCharge = actualCharge.sub(overestimation);
// After the relayed call has been executed and the actual charge estimated, the excess pre-charge is returned // After the relayed call has been executed and the actual charge estimated, the excess pre-charge is returned
...@@ -112,10 +111,10 @@ contract GSNRecipientERC20Fee is GSNRecipient { ...@@ -112,10 +111,10 @@ contract GSNRecipientERC20Fee is GSNRecipient {
* outside of this context. * outside of this context.
*/ */
// solhint-disable-next-line contract-name-camelcase // solhint-disable-next-line contract-name-camelcase
contract __unstable__ERC20Owned is ERC20, ERC20Detailed, Ownable { contract __unstable__ERC20Owned is ERC20, Ownable {
uint256 private constant UINT256_MAX = 2**256 - 1; uint256 private constant _UINT256_MAX = 2**256 - 1;
constructor(string memory name, string memory symbol, uint8 decimals) public ERC20Detailed(name, symbol, decimals) { } constructor(string memory name, string memory symbol) public ERC20(name, symbol) { }
// The owner (GSNRecipientERC20Fee) can mint tokens // The owner (GSNRecipientERC20Fee) can mint tokens
function mint(address account, uint256 amount) public onlyOwner { function mint(address account, uint256 amount) public onlyOwner {
...@@ -123,9 +122,9 @@ contract __unstable__ERC20Owned is ERC20, ERC20Detailed, Ownable { ...@@ -123,9 +122,9 @@ contract __unstable__ERC20Owned is ERC20, ERC20Detailed, Ownable {
} }
// The owner has 'infinite' allowance for all token holders // The owner has 'infinite' allowance for all token holders
function allowance(address tokenOwner, address spender) public view override(ERC20, IERC20) returns (uint256) { function allowance(address tokenOwner, address spender) public view override returns (uint256) {
if (spender == owner()) { if (spender == owner()) {
return UINT256_MAX; return _UINT256_MAX;
} else { } else {
return super.allowance(tokenOwner, spender); return super.allowance(tokenOwner, spender);
} }
...@@ -140,7 +139,7 @@ contract __unstable__ERC20Owned is ERC20, ERC20Detailed, Ownable { ...@@ -140,7 +139,7 @@ contract __unstable__ERC20Owned is ERC20, ERC20Detailed, Ownable {
} }
} }
function transferFrom(address sender, address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) { function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
if (recipient == owner()) { if (recipient == owner()) {
_transfer(sender, recipient, amount); _transfer(sender, recipient, amount);
return true; return true;
......
...@@ -32,15 +32,15 @@ contract GSNRecipientSignature is GSNRecipient { ...@@ -32,15 +32,15 @@ contract GSNRecipientSignature is GSNRecipient {
function acceptRelayedCall( function acceptRelayedCall(
address relay, address relay,
address from, address from,
bytes calldata encodedFunction, bytes memory encodedFunction,
uint256 transactionFee, uint256 transactionFee,
uint256 gasPrice, uint256 gasPrice,
uint256 gasLimit, uint256 gasLimit,
uint256 nonce, uint256 nonce,
bytes calldata approvalData, bytes memory approvalData,
uint256 uint256
) )
external public
view view
virtual virtual
override override
......
pragma solidity ^0.6.0; pragma solidity ^0.6.0;
import "../utils/EnumerableSet.sol"; import "../utils/EnumerableSet.sol";
import "../utils/Address.sol";
import "../GSN/Context.sol"; import "../GSN/Context.sol";
/** /**
...@@ -25,10 +26,7 @@ import "../GSN/Context.sol"; ...@@ -25,10 +26,7 @@ import "../GSN/Context.sol";
* } * }
* ``` * ```
* *
* Roles can be granted and revoked programatically by calling the `internal` * Roles can be granted and revoked dynamically via the {grantRole} and
* {_grantRole} and {_revokeRole} functions.
*
* This can also be achieved dynamically via the `external` {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only * {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRoke}. * accounts that have a role's admin role can call {grantRole} and {revokeRoke}.
* *
...@@ -39,6 +37,7 @@ import "../GSN/Context.sol"; ...@@ -39,6 +37,7 @@ import "../GSN/Context.sol";
*/ */
abstract contract AccessControl is Context { abstract contract AccessControl is Context {
using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.AddressSet;
using Address for address;
struct RoleData { struct RoleData {
EnumerableSet.AddressSet members; EnumerableSet.AddressSet members;
...@@ -52,9 +51,8 @@ abstract contract AccessControl is Context { ...@@ -52,9 +51,8 @@ abstract contract AccessControl is Context {
/** /**
* @dev Emitted when `account` is granted `role`. * @dev Emitted when `account` is granted `role`.
* *
* `sender` is the account that originated the contract call: * `sender` is the account that originated the contract call, an admin role
* - if using `grantRole`, it is the admin role bearer * bearer except when using {_setupRole}.
* - if using `_grantRole`, its meaning is system-dependent
*/ */
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
...@@ -64,7 +62,6 @@ abstract contract AccessControl is Context { ...@@ -64,7 +62,6 @@ abstract contract AccessControl is Context {
* `sender` is the account that originated the contract call: * `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer * - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`) * - if using `renounceRole`, it is the role bearer (i.e. `account`)
* - if using `_renounceRole`, its meaning is system-dependent
*/ */
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
...@@ -96,7 +93,7 @@ abstract contract AccessControl is Context { ...@@ -96,7 +93,7 @@ abstract contract AccessControl is Context {
* for more information. * for more information.
*/ */
function getRoleMember(bytes32 role, uint256 index) public view returns (address) { function getRoleMember(bytes32 role, uint256 index) public view returns (address) {
return _roles[role].members.get(index); return _roles[role].members.at(index);
} }
/** /**
...@@ -105,20 +102,21 @@ abstract contract AccessControl is Context { ...@@ -105,20 +102,21 @@ abstract contract AccessControl is Context {
* *
* To change a role's admin, use {_setRoleAdmin}. * To change a role's admin, use {_setRoleAdmin}.
*/ */
function getRoleAdmin(bytes32 role) external view returns (bytes32) { function getRoleAdmin(bytes32 role) public view returns (bytes32) {
return _roles[role].adminRole; return _roles[role].adminRole;
} }
/** /**
* @dev Grants `role` to `account`. * @dev Grants `role` to `account`.
* *
* Calls {_grantRole} internally. * If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
* *
* Requirements: * Requirements:
* *
* - the caller must have `role`'s admin role. * - the caller must have `role`'s admin role.
*/ */
function grantRole(bytes32 role, address account) external virtual { function grantRole(bytes32 role, address account) public virtual {
require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant"); require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant");
_grantRole(role, account); _grantRole(role, account);
...@@ -127,13 +125,13 @@ abstract contract AccessControl is Context { ...@@ -127,13 +125,13 @@ abstract contract AccessControl is Context {
/** /**
* @dev Revokes `role` from `account`. * @dev Revokes `role` from `account`.
* *
* Calls {_revokeRole} internally. * If `account` had been granted `role`, emits a {RoleRevoked} event.
* *
* Requirements: * Requirements:
* *
* - the caller must have `role`'s admin role. * - the caller must have `role`'s admin role.
*/ */
function revokeRole(bytes32 role, address account) external virtual { function revokeRole(bytes32 role, address account) public virtual {
require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke"); require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke");
_revokeRole(role, account); _revokeRole(role, account);
...@@ -146,11 +144,14 @@ abstract contract AccessControl is Context { ...@@ -146,11 +144,14 @@ abstract contract AccessControl is Context {
* purpose is to provide a mechanism for accounts to lose their privileges * purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced). * if they are compromised (such as when a trusted device is misplaced).
* *
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements: * Requirements:
* *
* - the caller must be `account`. * - the caller must be `account`.
*/ */
function renounceRole(bytes32 role, address account) external virtual { function renounceRole(bytes32 role, address account) public virtual {
require(account == _msgSender(), "AccessControl: can only renounce roles for self"); require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account); _revokeRole(role, account);
...@@ -160,23 +161,16 @@ abstract contract AccessControl is Context { ...@@ -160,23 +161,16 @@ abstract contract AccessControl is Context {
* @dev Grants `role` to `account`. * @dev Grants `role` to `account`.
* *
* If `account` had not been already granted `role`, emits a {RoleGranted} * If `account` had not been already granted `role`, emits a {RoleGranted}
* event. * event. Note that unlike {grantRole}, this function doesn't perform any
*/ * checks on the calling account.
function _grantRole(bytes32 role, address account) internal virtual {
if (_roles[role].members.add(account)) {
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
* *
* If `account` had been granted `role`, emits a {RoleRevoked} event. * Requirements:
*
* - this function can only be called from a constructor.
*/ */
function _revokeRole(bytes32 role, address account) internal virtual { function _setupRole(bytes32 role, address account) internal virtual {
if (_roles[role].members.remove(account)) { require(!address(this).isContract(), "AccessControl: roles cannot be setup after construction");
emit RoleRevoked(role, account, _msgSender()); _grantRole(role, account);
}
} }
/** /**
...@@ -185,4 +179,16 @@ abstract contract AccessControl is Context { ...@@ -185,4 +179,16 @@ abstract contract AccessControl is Context {
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
_roles[role].adminRole = adminRole; _roles[role].adminRole = adminRole;
} }
function _grantRole(bytes32 role, address account) private {
if (_roles[role].members.add(account)) {
emit RoleGranted(role, account, _msgSender());
}
}
function _revokeRole(bytes32 role, address account) private {
if (_roles[role].members.remove(account)) {
emit RoleRevoked(role, account, _msgSender());
}
}
} }
...@@ -59,13 +59,6 @@ contract Ownable is Context { ...@@ -59,13 +59,6 @@ contract Ownable is Context {
* Can only be called by the current owner. * Can only be called by the current owner.
*/ */
function transferOwnership(address newOwner) public virtual onlyOwner { function transferOwnership(address newOwner) public virtual onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
*/
function _transferOwnership(address newOwner) internal virtual {
require(newOwner != address(0), "Ownable: new owner is the zero address"); require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner); emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner; _owner = newOwner;
......
pragma solidity ^0.6.0;
import "../access/AccessControl.sol";
import "../GSN/Context.sol";
import "../token/ERC20/ERC20.sol";
import "../token/ERC20/ERC20Burnable.sol";
import "../token/ERC20/ERC20Pausable.sol";
/**
* @dev {ERC20} token, including:
*
* - ability for holders to burn (destroy) their tokens
* - a minter role that allows for token minting (creation)
* - a pauser role that allows to stop all token transfers
*
* This contract uses {AccessControl} to lock permissioned functions using the
* different roles - head to its documentation for details.
*
* The account that deploys the contract will be granted the minter role, the
* pauser role, and the default admin role, meaning it will be able to grant
* both the minter and pauser roles.
*/
contract ERC20MinterPauser is Context, AccessControl, ERC20Burnable, ERC20Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/**
* @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
* account that deploys the contract.
*
* See {ERC20-constructor}.
*/
constructor(string memory name, string memory symbol) public ERC20(name, symbol) {
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setupRole(MINTER_ROLE, _msgSender());
_setupRole(PAUSER_ROLE, _msgSender());
}
/**
* @dev Creates `amount` new tokens for `to`.
*
* See {ERC20-_mint}.
*
* Requirements:
*
* - the caller must have the `MINTER_ROLE`.
*/
function mint(address to, uint256 amount) public {
require(hasRole(MINTER_ROLE, _msgSender()), "ERC20MinterPauser: must have minter role to mint");
_mint(to, amount);
}
/**
* @dev Pauses all token transfers.
*
* See {ERC20Pausable} and {Pausable-_pause}.
*
* Requirements:
*
* - the caller must have the `PAUSER_ROLE`.
*/
function pause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20MinterPauser: must have pauser role to pause");
_pause();
}
/**
* @dev Unpauses all token transfers.
*
* See {ERC20Pausable} and {Pausable-_unpause}.
*
* Requirements:
*
* - the caller must have the `PAUSER_ROLE`.
*/
function unpause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20MinterPauser: must have pauser role to unpause");
_unpause();
}
function _beforeTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Pausable) {
super._beforeTokenTransfer(from, to, amount);
}
}
pragma solidity ^0.6.0;
import "../access/AccessControl.sol";
import "../GSN/Context.sol";
import "../token/ERC721/ERC721.sol";
import "../token/ERC721/ERC721Burnable.sol";
import "../token/ERC721/ERC721Pausable.sol";
/**
* @dev {ERC721} token, including:
*
* - ability for holders to burn (destroy) their tokens
* - a minter role that allows for token minting (creation)
* - a pauser role that allows to stop all token transfers
*
* This contract uses {AccessControl} to lock permissioned functions using the
* different roles - head to its documentation for details.
*
* The account that deploys the contract will be granted the minter role, the
* pauser role, and the default admin role, meaning it will be able to grant
* both the minter and pauser roles.
*/
contract ERC721MinterPauser is Context, AccessControl, ERC721Burnable, ERC721Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/**
* @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
* account that deploys the contract.
*
* See {ERC721-constructor}.
*/
constructor(string memory name, string memory symbol) public ERC721(name, symbol) {
_setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
_setupRole(MINTER_ROLE, _msgSender());
_setupRole(PAUSER_ROLE, _msgSender());
}
/**
* @dev Creates the `tokenId` tokens for `to`.
*
* See {ERC721-_mint}.
*
* Requirements:
*
* - the caller must have the `MINTER_ROLE`.
*/
function mint(address to, uint256 tokenId) public {
require(hasRole(MINTER_ROLE, _msgSender()), "ERC721MinterPauser: must have minter role to mint");
_mint(to, tokenId);
}
/**
* @dev Pauses all token transfers.
*
* See {ERC721Pausable} and {Pausable-_pause}.
*
* Requirements:
*
* - the caller must have the `PAUSER_ROLE`.
*/
function pause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721MinterPauser: must have pauser role to pause");
_pause();
}
/**
* @dev Unpauses all token transfers.
*
* See {ERC20Pausable} and {Pausable-_unpause}.
*
* Requirements:
*
* - the caller must have the `PAUSER_ROLE`.
*/
function unpause() public {
require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721MinterPauser: must have pauser role to unpause");
_unpause();
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Pausable) {
super._beforeTokenTransfer(from, to, tokenId);
}
}
= Deploy Ready
These contracts integrate different Ethereum standards (ERCs) with custom extensions and modules, showcasing common configurations that are ready to deploy **without having to write any Solidity code**.
They can be used as-is for quick prototyping and testing, but are **also suitable for production environments**.
TIP: Intermediate and advanced users can use these as starting points when writing their own contracts, extending them with custom functionality as they see fit.
== Tokens
{{ERC20MinterPauser}}
{{ERC721MinterPauser}}
...@@ -30,7 +30,7 @@ contract ERC165 is IERC165 { ...@@ -30,7 +30,7 @@ contract ERC165 is IERC165 {
* *
* Time complexity O(1), guaranteed to always use less than 30 000 gas. * Time complexity O(1), guaranteed to always use less than 30 000 gas.
*/ */
function supportsInterface(bytes4 interfaceId) external view override returns (bool) { function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return _supportedInterfaces[interfaceId]; return _supportedInterfaces[interfaceId];
} }
......
...@@ -19,7 +19,7 @@ library ERC165Checker { ...@@ -19,7 +19,7 @@ library ERC165Checker {
/** /**
* @dev Returns true if `account` supports the {IERC165} interface, * @dev Returns true if `account` supports the {IERC165} interface,
*/ */
function _supportsERC165(address account) internal view returns (bool) { function supportsERC165(address account) internal view returns (bool) {
// Any contract that implements ERC165 must explicitly indicate support of // Any contract that implements ERC165 must explicitly indicate support of
// InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
return _supportsERC165Interface(account, _INTERFACE_ID_ERC165) && return _supportsERC165Interface(account, _INTERFACE_ID_ERC165) &&
...@@ -32,9 +32,9 @@ library ERC165Checker { ...@@ -32,9 +32,9 @@ library ERC165Checker {
* *
* See {IERC165-supportsInterface}. * See {IERC165-supportsInterface}.
*/ */
function _supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) { function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
// query support of both ERC165 as per the spec and support of _interfaceId // query support of both ERC165 as per the spec and support of _interfaceId
return _supportsERC165(account) && return supportsERC165(account) &&
_supportsERC165Interface(account, interfaceId); _supportsERC165Interface(account, interfaceId);
} }
...@@ -47,9 +47,9 @@ library ERC165Checker { ...@@ -47,9 +47,9 @@ library ERC165Checker {
* *
* See {IERC165-supportsInterface}. * See {IERC165-supportsInterface}.
*/ */
function _supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) { function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
// query support of ERC165 itself // query support of ERC165 itself
if (!_supportsERC165(account)) { if (!supportsERC165(account)) {
return false; return false;
} }
...@@ -72,7 +72,7 @@ library ERC165Checker { ...@@ -72,7 +72,7 @@ library ERC165Checker {
* identifier interfaceId, false otherwise * identifier interfaceId, false otherwise
* @dev Assumes that account contains a contract that supports ERC165, otherwise * @dev Assumes that account contains a contract that supports ERC165, otherwise
* the behavior of this method is undefined. This precondition can be checked * the behavior of this method is undefined. This precondition can be checked
* with the `supportsERC165` method in this library. * with {supportsERC165}.
* Interface identification is specified in ERC-165. * Interface identification is specified in ERC-165.
*/ */
function _supportsERC165Interface(address account, bytes4 interfaceId) private view returns (bool) { function _supportsERC165Interface(address account, bytes4 interfaceId) private view returns (bool) {
......
...@@ -11,15 +11,15 @@ import "./IERC1820Implementer.sol"; ...@@ -11,15 +11,15 @@ import "./IERC1820Implementer.sol";
* registration to be complete. * registration to be complete.
*/ */
contract ERC1820Implementer is IERC1820Implementer { contract ERC1820Implementer is IERC1820Implementer {
bytes32 constant private ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC")); bytes32 constant private _ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));
mapping(bytes32 => mapping(address => bool)) private _supportedInterfaces; mapping(bytes32 => mapping(address => bool)) private _supportedInterfaces;
/** /**
* See {IERC1820Implementer-canImplementInterfaceForAddress}. * See {IERC1820Implementer-canImplementInterfaceForAddress}.
*/ */
function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) external view override returns (bytes32) { function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) public view override returns (bytes32) {
return _supportedInterfaces[interfaceHash][account] ? ERC1820_ACCEPT_MAGIC : bytes32(0x00); return _supportedInterfaces[interfaceHash][account] ? _ERC1820_ACCEPT_MAGIC : bytes32(0x00);
} }
/** /**
......
...@@ -5,7 +5,7 @@ pragma solidity ^0.6.0; ...@@ -5,7 +5,7 @@ pragma solidity ^0.6.0;
* @dev Signed math operations with safety checks that revert on error. * @dev Signed math operations with safety checks that revert on error.
*/ */
library SignedSafeMath { library SignedSafeMath {
int256 constant private INT256_MIN = -2**255; int256 constant private _INT256_MIN = -2**255;
/** /**
* @dev Multiplies two signed integers, reverts on overflow. * @dev Multiplies two signed integers, reverts on overflow.
...@@ -18,7 +18,7 @@ library SignedSafeMath { ...@@ -18,7 +18,7 @@ library SignedSafeMath {
return 0; return 0;
} }
require(!(a == -1 && b == INT256_MIN), "SignedSafeMath: multiplication overflow"); require(!(a == -1 && b == _INT256_MIN), "SignedSafeMath: multiplication overflow");
int256 c = a * b; int256 c = a * b;
require(c / a == b, "SignedSafeMath: multiplication overflow"); require(c / a == b, "SignedSafeMath: multiplication overflow");
...@@ -31,7 +31,7 @@ library SignedSafeMath { ...@@ -31,7 +31,7 @@ library SignedSafeMath {
*/ */
function div(int256 a, int256 b) internal pure returns (int256) { function div(int256 a, int256 b) internal pure returns (int256) {
require(b != 0, "SignedSafeMath: division by zero"); require(b != 0, "SignedSafeMath: division by zero");
require(!(b == -1 && a == INT256_MIN), "SignedSafeMath: division overflow"); require(!(b == -1 && a == _INT256_MIN), "SignedSafeMath: division overflow");
int256 c = a / b; int256 c = a / b;
......
...@@ -4,10 +4,14 @@ import "../access/AccessControl.sol"; ...@@ -4,10 +4,14 @@ import "../access/AccessControl.sol";
contract AccessControlMock is AccessControl { contract AccessControlMock is AccessControl {
constructor() public { constructor() public {
_grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
} }
function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public { function setRoleAdmin(bytes32 roleId, bytes32 adminRoleId) public {
_setRoleAdmin(roleId, adminRoleId); _setRoleAdmin(roleId, adminRoleId);
} }
function setupRole(bytes32 roleId, address account) public {
_setupRole(roleId, account);
}
} }
...@@ -5,13 +5,13 @@ import "../utils/Arrays.sol"; ...@@ -5,13 +5,13 @@ import "../utils/Arrays.sol";
contract ArraysImpl { contract ArraysImpl {
using Arrays for uint256[]; using Arrays for uint256[];
uint256[] private array; uint256[] private _array;
constructor (uint256[] memory _array) public { constructor (uint256[] memory array) public {
array = _array; _array = array;
} }
function findUpperBound(uint256 _element) external view returns (uint256) { function findUpperBound(uint256 element) external view returns (uint256) {
return array.findUpperBound(_element); return _array.findUpperBound(element);
} }
} }
pragma solidity ^0.6.0; pragma solidity ^0.6.0;
import "../utils/Create2.sol"; import "../utils/Create2.sol";
import "../token/ERC20/ERC20.sol"; import "../introspection/ERC1820Implementer.sol";
contract Create2Impl { contract Create2Impl {
function deploy(bytes32 salt, bytes memory code) public { function deploy(uint256 value, bytes32 salt, bytes memory code) public {
Create2.deploy(salt, code); Create2.deploy(value, salt, code);
} }
function deployERC20(bytes32 salt) public { function deployERC1820Implementer(uint256 value, bytes32 salt) public {
// solhint-disable-next-line indent // solhint-disable-next-line indent
Create2.deploy(salt, type(ERC20).creationCode); Create2.deploy(value, salt, type(ERC1820Implementer).creationCode);
} }
function computeAddress(bytes32 salt, bytes32 codeHash) public view returns (address) { function computeAddress(bytes32 salt, bytes32 codeHash) public view returns (address) {
...@@ -20,4 +20,6 @@ contract Create2Impl { ...@@ -20,4 +20,6 @@ contract Create2Impl {
function computeAddressWithDeployer(bytes32 salt, bytes32 codeHash, address deployer) public pure returns (address) { function computeAddressWithDeployer(bytes32 salt, bytes32 codeHash, address deployer) public pure returns (address) {
return Create2.computeAddress(salt, codeHash, deployer); return Create2.computeAddress(salt, codeHash, deployer);
} }
receive() payable external {}
} }
...@@ -34,7 +34,7 @@ contract SupportsInterfaceWithLookupMock is IERC165 { ...@@ -34,7 +34,7 @@ contract SupportsInterfaceWithLookupMock is IERC165 {
/** /**
* @dev Implement supportsInterface(bytes4) using a lookup table. * @dev Implement supportsInterface(bytes4) using a lookup table.
*/ */
function supportsInterface(bytes4 interfaceId) external view override returns (bool) { function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return _supportedInterfaces[interfaceId]; return _supportedInterfaces[interfaceId];
} }
......
...@@ -6,14 +6,14 @@ contract ERC165CheckerMock { ...@@ -6,14 +6,14 @@ contract ERC165CheckerMock {
using ERC165Checker for address; using ERC165Checker for address;
function supportsERC165(address account) public view returns (bool) { function supportsERC165(address account) public view returns (bool) {
return account._supportsERC165(); return account.supportsERC165();
} }
function supportsInterface(address account, bytes4 interfaceId) public view returns (bool) { function supportsInterface(address account, bytes4 interfaceId) public view returns (bool) {
return account._supportsInterface(interfaceId); return account.supportsInterface(interfaceId);
} }
function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) public view returns (bool) { function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) public view returns (bool) {
return account._supportsAllInterfaces(interfaceIds); return account.supportsAllInterfaces(interfaceIds);
} }
} }
...@@ -3,7 +3,12 @@ pragma solidity ^0.6.0; ...@@ -3,7 +3,12 @@ pragma solidity ^0.6.0;
import "../token/ERC20/ERC20Burnable.sol"; import "../token/ERC20/ERC20Burnable.sol";
contract ERC20BurnableMock is ERC20Burnable { contract ERC20BurnableMock is ERC20Burnable {
constructor (address initialAccount, uint256 initialBalance) public { constructor (
string memory name,
string memory symbol,
address initialAccount,
uint256 initialBalance
) public ERC20(name, symbol) {
_mint(initialAccount, initialBalance); _mint(initialAccount, initialBalance);
} }
} }
...@@ -3,7 +3,9 @@ pragma solidity ^0.6.0; ...@@ -3,7 +3,9 @@ pragma solidity ^0.6.0;
import "../token/ERC20/ERC20Capped.sol"; import "../token/ERC20/ERC20Capped.sol";
contract ERC20CappedMock is ERC20Capped { contract ERC20CappedMock is ERC20Capped {
constructor (uint256 cap) public ERC20Capped(cap) { } constructor (string memory name, string memory symbol, uint256 cap)
public ERC20(name, symbol) ERC20Capped(cap)
{ }
function mint(address to, uint256 tokenId) public { function mint(address to, uint256 tokenId) public {
_mint(to, tokenId); _mint(to, tokenId);
......
pragma solidity ^0.6.0;
import "../token/ERC20/ERC20.sol";
contract ERC20DecimalsMock is ERC20 {
constructor (string memory name, string memory symbol, uint8 decimals) public ERC20(name, symbol) {
_setupDecimals(decimals);
}
function setupDecimals(uint8 decimals) public {
_setupDecimals(decimals);
}
}
pragma solidity ^0.6.0;
import "../token/ERC20/ERC20.sol";
import "../token/ERC20/ERC20Detailed.sol";
contract ERC20DetailedMock is ERC20, ERC20Detailed {
constructor (string memory name, string memory symbol, uint8 decimals)
public
ERC20Detailed(name, symbol, decimals)
{
}
}
...@@ -4,7 +4,12 @@ import "../token/ERC20/ERC20.sol"; ...@@ -4,7 +4,12 @@ import "../token/ERC20/ERC20.sol";
// mock class using ERC20 // mock class using ERC20
contract ERC20Mock is ERC20 { contract ERC20Mock is ERC20 {
constructor (address initialAccount, uint256 initialBalance) public { constructor (
string memory name,
string memory symbol,
address initialAccount,
uint256 initialBalance
) public payable ERC20(name, symbol) {
_mint(initialAccount, initialBalance); _mint(initialAccount, initialBalance);
} }
......
...@@ -4,7 +4,12 @@ import "../token/ERC20/ERC20Pausable.sol"; ...@@ -4,7 +4,12 @@ import "../token/ERC20/ERC20Pausable.sol";
// mock class using ERC20Pausable // mock class using ERC20Pausable
contract ERC20PausableMock is ERC20Pausable { contract ERC20PausableMock is ERC20Pausable {
constructor (address initialAccount, uint256 initialBalance) public { constructor (
string memory name,
string memory symbol,
address initialAccount,
uint256 initialBalance
) public ERC20(name, symbol) {
_mint(initialAccount, initialBalance); _mint(initialAccount, initialBalance);
} }
......
...@@ -4,7 +4,12 @@ import "../token/ERC20/ERC20Snapshot.sol"; ...@@ -4,7 +4,12 @@ import "../token/ERC20/ERC20Snapshot.sol";
contract ERC20SnapshotMock is ERC20Snapshot { contract ERC20SnapshotMock is ERC20Snapshot {
constructor(address initialAccount, uint256 initialBalance) public { constructor(
string memory name,
string memory symbol,
address initialAccount,
uint256 initialBalance
) public ERC20(name, symbol) {
_mint(initialAccount, initialBalance); _mint(initialAccount, initialBalance);
} }
......
...@@ -3,6 +3,8 @@ pragma solidity ^0.6.0; ...@@ -3,6 +3,8 @@ pragma solidity ^0.6.0;
import "../token/ERC721/ERC721Burnable.sol"; import "../token/ERC721/ERC721Burnable.sol";
contract ERC721BurnableMock is ERC721Burnable { contract ERC721BurnableMock is ERC721Burnable {
constructor(string memory name, string memory symbol) public ERC721(name, symbol) { }
function mint(address to, uint256 tokenId) public { function mint(address to, uint256 tokenId) public {
_mint(to, tokenId); _mint(to, tokenId);
} }
......
pragma solidity ^0.6.0;
import "../token/ERC721/ERC721Full.sol";
import "../token/ERC721/ERC721Burnable.sol";
/**
* @title ERC721FullMock
* This mock just provides public functions for setting metadata URI, getting all tokens of an owner,
* checking token existence, removal of a token from an address
*/
contract ERC721FullMock is ERC721Full, ERC721Burnable {
constructor (string memory name, string memory symbol) public ERC721Full(name, symbol) { }
function exists(uint256 tokenId) public view returns (bool) {
return _exists(tokenId);
}
function tokensOfOwner(address owner) public view returns (uint256[] memory) {
return _tokensOfOwner(owner);
}
function setTokenURI(uint256 tokenId, string memory uri) public {
_setTokenURI(tokenId, uri);
}
function setBaseURI(string memory baseURI) public {
_setBaseURI(baseURI);
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override(ERC721, ERC721Full) {
super._beforeTokenTransfer(from, to, tokenId);
}
function mint(address to, uint256 tokenId) public {
_mint(to, tokenId);
}
}
...@@ -9,7 +9,11 @@ import "../GSN/GSNRecipientSignature.sol"; ...@@ -9,7 +9,11 @@ import "../GSN/GSNRecipientSignature.sol";
* A simple ERC721 mock that has GSN support enabled * A simple ERC721 mock that has GSN support enabled
*/ */
contract ERC721GSNRecipientMock is ERC721, GSNRecipient, GSNRecipientSignature { contract ERC721GSNRecipientMock is ERC721, GSNRecipient, GSNRecipientSignature {
constructor(address trustedSigner) public GSNRecipientSignature(trustedSigner) { } constructor(string memory name, string memory symbol, address trustedSigner)
public
ERC721(name, symbol)
GSNRecipientSignature(trustedSigner)
{ }
function mint(uint256 tokenId) public { function mint(uint256 tokenId) public {
_mint(_msgSender(), tokenId); _mint(_msgSender(), tokenId);
......
...@@ -7,18 +7,32 @@ import "../token/ERC721/ERC721.sol"; ...@@ -7,18 +7,32 @@ import "../token/ERC721/ERC721.sol";
* This mock just provides a public safeMint, mint, and burn functions for testing purposes * This mock just provides a public safeMint, mint, and burn functions for testing purposes
*/ */
contract ERC721Mock is ERC721 { contract ERC721Mock is ERC721 {
function safeMint(address to, uint256 tokenId) public { constructor (string memory name, string memory symbol) public ERC721(name, symbol) { }
_safeMint(to, tokenId);
function exists(uint256 tokenId) public view returns (bool) {
return _exists(tokenId);
} }
function safeMint(address to, uint256 tokenId, bytes memory _data) public { function setTokenURI(uint256 tokenId, string memory uri) public {
_safeMint(to, tokenId, _data); _setTokenURI(tokenId, uri);
}
function setBaseURI(string memory baseURI) public {
_setBaseURI(baseURI);
} }
function mint(address to, uint256 tokenId) public { function mint(address to, uint256 tokenId) public {
_mint(to, tokenId); _mint(to, tokenId);
} }
function safeMint(address to, uint256 tokenId) public {
_safeMint(to, tokenId);
}
function safeMint(address to, uint256 tokenId, bytes memory _data) public {
_safeMint(to, tokenId, _data);
}
function burn(uint256 tokenId) public { function burn(uint256 tokenId) public {
_burn(tokenId); _burn(tokenId);
} }
......
...@@ -7,6 +7,8 @@ import "../token/ERC721/ERC721Pausable.sol"; ...@@ -7,6 +7,8 @@ import "../token/ERC721/ERC721Pausable.sol";
* This mock just provides a public mint, burn and exists functions for testing purposes * This mock just provides a public mint, burn and exists functions for testing purposes
*/ */
contract ERC721PausableMock is ERC721Pausable { contract ERC721PausableMock is ERC721Pausable {
constructor (string memory name, string memory symbol) public ERC721(name, symbol) { }
function mint(address to, uint256 tokenId) public { function mint(address to, uint256 tokenId) public {
super._mint(to, tokenId); super._mint(to, tokenId);
} }
......
...@@ -37,8 +37,8 @@ contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient, ...@@ -37,8 +37,8 @@ contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient,
IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
bytes32 constant private TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender"); bytes32 constant private _TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender");
bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); bytes32 constant private _TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient");
function tokensToSend( function tokensToSend(
address operator, address operator,
...@@ -103,7 +103,7 @@ contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient, ...@@ -103,7 +103,7 @@ contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient,
} }
function senderFor(address account) public { function senderFor(address account) public {
_registerInterfaceForAddress(TOKENS_SENDER_INTERFACE_HASH, account); _registerInterfaceForAddress(_TOKENS_SENDER_INTERFACE_HASH, account);
address self = address(this); address self = address(this);
if (account == self) { if (account == self) {
...@@ -112,11 +112,11 @@ contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient, ...@@ -112,11 +112,11 @@ contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient,
} }
function registerSender(address sender) public { function registerSender(address sender) public {
_erc1820.setInterfaceImplementer(address(this), TOKENS_SENDER_INTERFACE_HASH, sender); _erc1820.setInterfaceImplementer(address(this), _TOKENS_SENDER_INTERFACE_HASH, sender);
} }
function recipientFor(address account) public { function recipientFor(address account) public {
_registerInterfaceForAddress(TOKENS_RECIPIENT_INTERFACE_HASH, account); _registerInterfaceForAddress(_TOKENS_RECIPIENT_INTERFACE_HASH, account);
address self = address(this); address self = address(this);
if (account == self) { if (account == self) {
...@@ -125,7 +125,7 @@ contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient, ...@@ -125,7 +125,7 @@ contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient,
} }
function registerRecipient(address recipient) public { function registerRecipient(address recipient) public {
_erc1820.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, recipient); _erc1820.setInterfaceImplementer(address(this), _TOKENS_RECIPIENT_INTERFACE_HASH, recipient);
} }
function setShouldRevertSend(bool shouldRevert) public { function setShouldRevertSend(bool shouldRevert) public {
......
pragma solidity ^0.6.0;
import "../utils/EnumerableMap.sol";
contract EnumerableMapMock {
using EnumerableMap for EnumerableMap.UintToAddressMap;
event OperationResult(bool result);
EnumerableMap.UintToAddressMap private _map;
function contains(uint256 key) public view returns (bool) {
return _map.contains(key);
}
function set(uint256 key, address value) public {
bool result = _map.set(key, value);
emit OperationResult(result);
}
function remove(uint256 key) public {
bool result = _map.remove(key);
emit OperationResult(result);
}
function length() public view returns (uint256) {
return _map.length();
}
function at(uint256 index) public view returns (uint256 key, address value) {
return _map.at(index);
}
function get(uint256 key) public view returns (address) {
return _map.get(key);
}
}
...@@ -2,36 +2,32 @@ pragma solidity ^0.6.0; ...@@ -2,36 +2,32 @@ pragma solidity ^0.6.0;
import "../utils/EnumerableSet.sol"; import "../utils/EnumerableSet.sol";
contract EnumerableSetMock{ contract EnumerableSetMock {
using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.AddressSet;
event TransactionResult(bool result); event OperationResult(bool result);
EnumerableSet.AddressSet private set; EnumerableSet.AddressSet private _set;
function contains(address value) public view returns (bool) { function contains(address value) public view returns (bool) {
return set.contains(value); return _set.contains(value);
} }
function add(address value) public { function add(address value) public {
bool result = set.add(value); bool result = _set.add(value);
emit TransactionResult(result); emit OperationResult(result);
} }
function remove(address value) public { function remove(address value) public {
bool result = set.remove(value); bool result = _set.remove(value);
emit TransactionResult(result); emit OperationResult(result);
}
function enumerate() public view returns (address[] memory) {
return set.enumerate();
} }
function length() public view returns (uint256) { function length() public view returns (uint256) {
return set.length(); return _set.length();
} }
function get(uint256 index) public view returns (address) { function at(uint256 index) public view returns (address) {
return set.get(index); return _set.at(index);
} }
} }
...@@ -11,19 +11,19 @@ contract ReentrancyMock is ReentrancyGuard { ...@@ -11,19 +11,19 @@ contract ReentrancyMock is ReentrancyGuard {
} }
function callback() external nonReentrant { function callback() external nonReentrant {
count(); _count();
} }
function countLocalRecursive(uint256 n) public nonReentrant { function countLocalRecursive(uint256 n) public nonReentrant {
if (n > 0) { if (n > 0) {
count(); _count();
countLocalRecursive(n - 1); countLocalRecursive(n - 1);
} }
} }
function countThisRecursive(uint256 n) public nonReentrant { function countThisRecursive(uint256 n) public nonReentrant {
if (n > 0) { if (n > 0) {
count(); _count();
// solhint-disable-next-line avoid-low-level-calls // solhint-disable-next-line avoid-low-level-calls
(bool success,) = address(this).call(abi.encodeWithSignature("countThisRecursive(uint256)", n - 1)); (bool success,) = address(this).call(abi.encodeWithSignature("countThisRecursive(uint256)", n - 1));
require(success, "ReentrancyMock: failed call"); require(success, "ReentrancyMock: failed call");
...@@ -31,12 +31,12 @@ contract ReentrancyMock is ReentrancyGuard { ...@@ -31,12 +31,12 @@ contract ReentrancyMock is ReentrancyGuard {
} }
function countAndCall(ReentrancyAttack attacker) public nonReentrant { function countAndCall(ReentrancyAttack attacker) public nonReentrant {
count(); _count();
bytes4 func = bytes4(keccak256("callback()")); bytes4 func = bytes4(keccak256("callback()"));
attacker.callSender(func); attacker.callSender(func);
} }
function count() private { function _count() private {
counter += 1; counter += 1;
} }
} }
...@@ -4,6 +4,15 @@ import "../utils/SafeCast.sol"; ...@@ -4,6 +4,15 @@ import "../utils/SafeCast.sol";
contract SafeCastMock { contract SafeCastMock {
using SafeCast for uint; using SafeCast for uint;
using SafeCast for int;
function toUint256(int a) public pure returns (uint256) {
return a.toUint256();
}
function toInt256(uint a) public pure returns (int256) {
return a.toInt256();
}
function toUint128(uint a) public pure returns (uint128) { function toUint128(uint a) public pure returns (uint128) {
return a.toUint128(); return a.toUint128();
......
...@@ -3,6 +3,7 @@ pragma solidity ^0.6.0; ...@@ -3,6 +3,7 @@ pragma solidity ^0.6.0;
import "../../GSN/Context.sol"; import "../../GSN/Context.sol";
import "./IERC20.sol"; import "./IERC20.sol";
import "../../math/SafeMath.sol"; import "../../math/SafeMath.sol";
import "../../utils/Address.sol";
/** /**
* @dev Implementation of the {IERC20} interface. * @dev Implementation of the {IERC20} interface.
...@@ -30,6 +31,7 @@ import "../../math/SafeMath.sol"; ...@@ -30,6 +31,7 @@ import "../../math/SafeMath.sol";
*/ */
contract ERC20 is Context, IERC20 { contract ERC20 is Context, IERC20 {
using SafeMath for uint256; using SafeMath for uint256;
using Address for address;
mapping (address => uint256) private _balances; mapping (address => uint256) private _balances;
...@@ -37,6 +39,57 @@ contract ERC20 is Context, IERC20 { ...@@ -37,6 +39,57 @@ contract ERC20 is Context, IERC20 {
uint256 private _totalSupply; uint256 private _totalSupply;
string private _name;
string private _symbol;
uint8 private _decimals;
/**
* @dev Sets the values for {name} and {symbol}, initializes {decimals} with
* a default value of 18.
*
* To select a different value for {decimals}, use {_setupDecimals}.
*
* All three of these values are immutable: they can only be set once during
* construction.
*/
constructor (string memory name, string memory symbol) public {
_name = name;
_symbol = symbol;
_decimals = 18;
}
/**
* @dev Returns the name of the token.
*/
function name() public view returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5,05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
* called.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view returns (uint8) {
return _decimals;
}
/** /**
* @dev See {IERC20-totalSupply}. * @dev See {IERC20-totalSupply}.
*/ */
...@@ -224,6 +277,18 @@ contract ERC20 is Context, IERC20 { ...@@ -224,6 +277,18 @@ contract ERC20 is Context, IERC20 {
} }
/** /**
* @dev Sets {decimals} to a value other than the default one of 18.
*
* Requirements:
*
* - this function can only be called from a constructor.
*/
function _setupDecimals(uint8 decimals_) internal {
require(!address(this).isContract(), "ERC20: decimals cannot be changed after construction");
_decimals = decimals_;
}
/**
* @dev Hook that is called before any transfer of tokens. This includes * @dev Hook that is called before any transfer of tokens. This includes
* minting and burning. * minting and burning.
* *
......
...@@ -8,7 +8,7 @@ import "./ERC20.sol"; ...@@ -8,7 +8,7 @@ import "./ERC20.sol";
* tokens and those that they have an allowance for, in a way that can be * tokens and those that they have an allowance for, in a way that can be
* recognized off-chain (via event analysis). * recognized off-chain (via event analysis).
*/ */
contract ERC20Burnable is Context, ERC20 { abstract contract ERC20Burnable is Context, ERC20 {
/** /**
* @dev Destroys `amount` tokens from the caller. * @dev Destroys `amount` tokens from the caller.
* *
......
...@@ -5,7 +5,7 @@ import "./ERC20.sol"; ...@@ -5,7 +5,7 @@ import "./ERC20.sol";
/** /**
* @dev Extension of {ERC20} that adds a cap to the supply of tokens. * @dev Extension of {ERC20} that adds a cap to the supply of tokens.
*/ */
contract ERC20Capped is ERC20 { abstract contract ERC20Capped is ERC20 {
uint256 private _cap; uint256 private _cap;
/** /**
......
pragma solidity ^0.6.0;
import "./IERC20.sol";
/**
* @dev Optional functions from the ERC20 standard.
*/
abstract contract ERC20Detailed is IERC20 {
string private _name;
string private _symbol;
uint8 private _decimals;
/**
* @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
* these values are immutable: they can only be set once during
* construction.
*/
constructor (string memory name, string memory symbol, uint8 decimals) public {
_name = name;
_symbol = symbol;
_decimals = decimals;
}
/**
* @dev Returns the name of the token.
*/
function name() public view returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5,05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view returns (uint8) {
return _decimals;
}
}
...@@ -11,7 +11,7 @@ import "../../utils/Pausable.sol"; ...@@ -11,7 +11,7 @@ import "../../utils/Pausable.sol";
* period, or having an emergency switch for freezing all token transfers in the * period, or having an emergency switch for freezing all token transfers in the
* event of a large bug. * event of a large bug.
*/ */
contract ERC20Pausable is ERC20, Pausable { abstract contract ERC20Pausable is ERC20, Pausable {
/** /**
* @dev See {ERC20-_beforeTokenTransfer}. * @dev See {ERC20-_beforeTokenTransfer}.
* *
......
...@@ -17,7 +17,7 @@ import "./ERC20.sol"; ...@@ -17,7 +17,7 @@ import "./ERC20.sol";
* account address. * account address.
* @author Validity Labs AG <info@validitylabs.org> * @author Validity Labs AG <info@validitylabs.org>
*/ */
contract ERC20Snapshot is ERC20 { abstract contract ERC20Snapshot is ERC20 {
// Inspired by Jordi Baylina's MiniMeToken to record historical balances: // Inspired by Jordi Baylina's MiniMeToken to record historical balances:
// https://github.com/Giveth/minimd/blob/ea04d950eea153a04c51fa510b068b9dded390cb/contracts/MiniMeToken.sol // https://github.com/Giveth/minimd/blob/ea04d950eea153a04c51fa510b068b9dded390cb/contracts/MiniMeToken.sol
......
...@@ -2,48 +2,40 @@ ...@@ -2,48 +2,40 @@
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:tokens.adoc#ERC20[ERC20 guide]. 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].
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:
* {IERC20}: the interface all ERC20 implementations should conform to * {IERC20}: the interface all ERC20 implementations should conform to.
* {ERC20}: the base implementation of the ERC20 interface * {ERC20}: the implementation of the ERC20 interface, including the <<ERC20-name,`name`>>, <<ERC20-symbol,`symbol`>> and <<ERC20-decimals,`decimals`>> optional standard extension to the base interface.
* {ERC20Detailed}: includes the <<ERC20Detailed-name,`name`>>,
<<ERC20Detailed-symbol,`symbol`>> and <<ERC20Detailed-decimals,`decimals`>>
optional standard extension to the base interface
Additionally there are multiple custom extensions, including: Additionally there are multiple custom extensions, including:
* designation of addresses that can create token supply ({ERC20Mintable}), with an optional maximum cap ({ERC20Capped}) * designation of addresses that can pause token transfers for all users ({ERC20Pausable}).
* destruction of own tokens ({ERC20Burnable}) * efficient storage of past token balances to be later queried at any point in time ({ERC20Snapshot}).
* designation of addresses that can pause token operations for all users ({ERC20Pausable}). * destruction of own tokens ({ERC20Burnable}).
* enforcement of a cap to the total supply when minting tokens ({ERC20Capped}).
Finally, there are some utilities to interact with ERC20 contracts in various ways. Finally, there are some utilities to interact with ERC20 contracts in various ways.
* {SafeERC20} is a wrapper around the interface that eliminates the need to handle boolean return values. * {SafeERC20} is a wrapper around the interface that eliminates the need to handle boolean return values.
* {TokenTimelock} can hold tokens for a beneficiary until a specified time. * {TokenTimelock} can hold tokens for a beneficiary until a specified time.
NOTE: This page is incomplete. We're working to improve it for the next release. Stay tuned!
== Core == Core
{{IERC20}} {{IERC20}}
{{ERC20}} {{ERC20}}
{{ERC20Detailed}}
== Extensions == Extensions
{{ERC20Mintable}} {{ERC20Snapshot}}
{{ERC20Burnable}}
{{ERC20Pausable}} {{ERC20Pausable}}
{{ERC20Capped}} {{ERC20Burnable}}
{{ERC20Snapshot}} {{ERC20Capped}}
== Utilities == Utilities
......
...@@ -18,11 +18,11 @@ library SafeERC20 { ...@@ -18,11 +18,11 @@ library SafeERC20 {
using Address for address; using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal { function safeTransfer(IERC20 token, address to, uint256 value) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
} }
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
} }
function safeApprove(IERC20 token, address spender, uint256 value) internal { function safeApprove(IERC20 token, address spender, uint256 value) internal {
...@@ -33,17 +33,17 @@ library SafeERC20 { ...@@ -33,17 +33,17 @@ library SafeERC20 {
require((value == 0) || (token.allowance(address(this), spender) == 0), require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance" "SafeERC20: approve from non-zero to non-zero allowance"
); );
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
} }
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value); uint256 newAllowance = token.allowance(address(this), spender).add(value);
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
} }
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
} }
/** /**
...@@ -52,7 +52,7 @@ library SafeERC20 { ...@@ -52,7 +52,7 @@ library SafeERC20 {
* @param token The token targeted by the call. * @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants). * @param data The call data (encoded using abi.encode or one of its variants).
*/ */
function callOptionalReturn(IERC20 token, bytes memory data) private { function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. // we're implementing it ourselves.
......
...@@ -7,7 +7,7 @@ import "./ERC721.sol"; ...@@ -7,7 +7,7 @@ import "./ERC721.sol";
* @title ERC721 Burnable Token * @title ERC721 Burnable Token
* @dev ERC721 Token that can be irreversibly burned (destroyed). * @dev ERC721 Token that can be irreversibly burned (destroyed).
*/ */
contract ERC721Burnable is Context, ERC721 { abstract contract ERC721Burnable is Context, ERC721 {
/** /**
* @dev Burns a specific ERC721 token. * @dev Burns a specific ERC721 token.
* @param tokenId uint256 id of the ERC721 token to be burned. * @param tokenId uint256 id of the ERC721 token to be burned.
......
pragma solidity ^0.6.0;
import "../../GSN/Context.sol";
import "./IERC721Enumerable.sol";
import "./ERC721.sol";
import "../../introspection/ERC165.sol";
/**
* @title ERC-721 Non-Fungible Token with optional enumeration extension logic
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
contract ERC721Enumerable is Context, ERC165, ERC721, IERC721Enumerable {
// Mapping from owner to list of owned token IDs
mapping(address => uint256[]) private _ownedTokens;
// Mapping from token ID to index of the owner tokens list
mapping(uint256 => uint256) private _ownedTokensIndex;
// Array with all token ids, used for enumeration
uint256[] private _allTokens;
// Mapping from token id to position in the allTokens array
mapping(uint256 => uint256) private _allTokensIndex;
/*
* bytes4(keccak256('totalSupply()')) == 0x18160ddd
* bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59
* bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7
*
* => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63
*/
bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;
/**
* @dev Constructor function.
*/
constructor () public {
// register the supported interface to conform to ERC721Enumerable via ERC165
_registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
}
/**
* @dev Gets the token ID at a given index of the tokens list of the requested owner.
* @param owner address owning the tokens list to be accessed
* @param index uint256 representing the index to be accessed of the requested tokens list
* @return uint256 token ID at the given index of the tokens list owned by the requested address
*/
function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) {
require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
return _ownedTokens[owner][index];
}
/**
* @dev Gets the total amount of tokens stored by the contract.
* @return uint256 representing the total amount of tokens
*/
function totalSupply() public view override returns (uint256) {
return _allTokens.length;
}
/**
* @dev Gets the token ID at a given index of all the tokens in this contract
* Reverts if the index is greater or equal to the total number of tokens.
* @param index uint256 representing the index to be accessed of the tokens list
* @return uint256 token ID at the given index of the tokens list
*/
function tokenByIndex(uint256 index) public view override returns (uint256) {
require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
return _allTokens[index];
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {
super._beforeTokenTransfer(from, to, tokenId);
if (from == address(0)) {
// When minting
_addTokenToOwnerEnumeration(to, tokenId);
_addTokenToAllTokensEnumeration(tokenId);
} else if (to == address(0)) {
// When burning
_removeTokenFromOwnerEnumeration(from, tokenId);
// Since tokenId will be deleted, we can clear its slot in _ownedTokensIndex to trigger a gas refund
_ownedTokensIndex[tokenId] = 0;
_removeTokenFromAllTokensEnumeration(tokenId);
} else {
_removeTokenFromOwnerEnumeration(from, tokenId);
_addTokenToOwnerEnumeration(to, tokenId);
}
}
/**
* @dev Gets the list of token IDs of the requested owner.
* @param owner address owning the tokens
* @return uint256[] List of token IDs owned by the requested address
*/
function _tokensOfOwner(address owner) internal view returns (uint256[] storage) {
return _ownedTokens[owner];
}
/**
* @dev Private function to add a token to this extension's ownership-tracking data structures.
* @param to address representing the new owner of the given token ID
* @param tokenId uint256 ID of the token to be added to the tokens list of the given address
*/
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
_ownedTokensIndex[tokenId] = _ownedTokens[to].length;
_ownedTokens[to].push(tokenId);
}
/**
* @dev Private function to add a token to this extension's token tracking data structures.
* @param tokenId uint256 ID of the token to be added to the tokens list
*/
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
_allTokensIndex[tokenId] = _allTokens.length;
_allTokens.push(tokenId);
}
/**
* @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
* while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
* gas optimizations e.g. when performing a transfer operation (avoiding double writes).
* This has O(1) time complexity, but alters the order of the _ownedTokens array.
* @param from address representing the previous owner of the given token ID
* @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
*/
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = _ownedTokens[from].length.sub(1);
uint256 tokenIndex = _ownedTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary
if (tokenIndex != lastTokenIndex) {
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
}
// Deletes the contents at the last position of the array
_ownedTokens[from].pop();
// Note that _ownedTokensIndex[tokenId] hasn't been cleared: it still points to the old slot (now occupied by
// lastTokenId, or just over the end of the array if the token was the last one).
}
/**
* @dev Private function to remove a token from this extension's token tracking data structures.
* This has O(1) time complexity, but alters the order of the _allTokens array.
* @param tokenId uint256 ID of the token to be removed from the tokens list
*/
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
// To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = _allTokens.length.sub(1);
uint256 tokenIndex = _allTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
// rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
// an 'if' statement (like in _removeTokenFromOwnerEnumeration)
uint256 lastTokenId = _allTokens[lastTokenIndex];
_allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
// Delete the contents at the last position of the array
_allTokens.pop();
_allTokensIndex[tokenId] = 0;
}
}
pragma solidity ^0.6.0;
import "./ERC721Enumerable.sol";
import "./ERC721Metadata.sol";
/**
* @title Full ERC721 Token
* @dev This implementation includes all the required and some optional functionality of the ERC721 standard
* Moreover, it includes approve all functionality using operator terminology.
*
* See https://eips.ethereum.org/EIPS/eip-721
*/
contract ERC721Full is ERC721Enumerable, ERC721Metadata {
constructor (string memory name, string memory symbol) public ERC721Metadata(name, symbol) { }
function _beforeTokenTransfer(address from, address to, uint256 tokenId)
virtual
override(ERC721Enumerable, ERC721Metadata)
internal
{
super._beforeTokenTransfer(from, to, tokenId);
}
}
pragma solidity ^0.6.0;
import "../../GSN/Context.sol";
import "./ERC721.sol";
import "./IERC721Metadata.sol";
import "../../introspection/ERC165.sol";
contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata {
// Token name
string private _name;
// Token symbol
string private _symbol;
// Optional mapping for token URIs
mapping(uint256 => string) private _tokenURIs;
// Base URI
string private _baseURI;
/*
* bytes4(keccak256('name()')) == 0x06fdde03
* bytes4(keccak256('symbol()')) == 0x95d89b41
* bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd
*
* => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f
*/
bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;
/**
* @dev Constructor function
*/
constructor (string memory name, string memory symbol) public {
_name = name;
_symbol = symbol;
// register the supported interfaces to conform to ERC721 via ERC165
_registerInterface(_INTERFACE_ID_ERC721_METADATA);
}
/**
* @dev Gets the token name.
* @return string representing the token name
*/
function name() external view override returns (string memory) {
return _name;
}
/**
* @dev Gets the token symbol.
* @return string representing the token symbol
*/
function symbol() external view override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the URI for a given token ID. May return an empty string.
*
* If the token's URI is non-empty and a base URI was set (via
* {_setBaseURI}), it will be added to the token ID's URI as a prefix.
*
* Reverts if the token ID does not exist.
*/
function tokenURI(uint256 tokenId) external view override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory _tokenURI = _tokenURIs[tokenId];
// Even if there is a base URI, it is only appended to non-empty token-specific URIs
if (bytes(_tokenURI).length == 0) {
return "";
} else {
// abi.encodePacked is being used to concatenate strings
return string(abi.encodePacked(_baseURI, _tokenURI));
}
}
/**
* @dev Internal function to set the token URI for a given token.
*
* Reverts if the token ID does not exist.
*
* TIP: if all token IDs share a prefix (e.g. if your URIs look like
* `http://api.myproject.com/token/<id>`), use {_setBaseURI} to store
* it and save gas.
*/
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
_tokenURIs[tokenId] = _tokenURI;
}
/**
* @dev Internal function to set the base URI for all token IDs. It is
* automatically added as a prefix to the value returned in {tokenURI}.
*/
function _setBaseURI(string memory baseURI) internal virtual {
_baseURI = baseURI;
}
/**
* @dev Returns the base URI set via {_setBaseURI}. This will be
* automatically added as a preffix in {tokenURI} to each token's URI, when
* they are non-empty.
*/
function baseURI() external view returns (string memory) {
return _baseURI;
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {
super._beforeTokenTransfer(from, to, tokenId);
if (to == address(0)) { // When burning tokens
// Clear metadata (if any)
if (bytes(_tokenURIs[tokenId]).length != 0) {
delete _tokenURIs[tokenId];
}
}
}
}
...@@ -7,7 +7,7 @@ import "../../utils/Pausable.sol"; ...@@ -7,7 +7,7 @@ import "../../utils/Pausable.sol";
* @title ERC721 Non-Fungible Pausable token * @title ERC721 Non-Fungible Pausable token
* @dev ERC721 modified with pausable transfers. * @dev ERC721 modified with pausable transfers.
*/ */
contract ERC721Pausable is ERC721, Pausable { abstract contract ERC721Pausable is ERC721, Pausable {
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override { function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {
super._beforeTokenTransfer(from, to, tokenId); super._beforeTokenTransfer(from, to, tokenId);
......
pragma solidity ^0.6.0;
import "./IERC721.sol";
import "./IERC721Enumerable.sol";
import "./IERC721Metadata.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, full implementation interface
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
abstract contract IERC721Full is IERC721, IERC721Enumerable, IERC721Metadata { }
...@@ -2,54 +2,37 @@ ...@@ -2,54 +2,37 @@
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:tokens.adoc#ERC721[ERC721 guide]. TIP: For a walkthrough 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. 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}.
Each interface is implemented separately in {ERC721}, {ERC721Metadata}, and {ERC721Enumerable}. You can choose the subset of functionality you would like to support in your token by combining the
desired subset through inheritance.
The fully featured token implementing all three interfaces is prepackaged as {ERC721Full}.
Additionally, {IERC721Receiver} can be used to prevent tokens from becoming forever locked in contracts. Imagine sending an in-game item to an exchange address that can't send it back!. When using <<IERC721-safeTransferFrom,`safeTransferFrom`>>, the token contract checks to see that the receiver is an {IERC721Receiver}, which implies that it knows how to handle {ERC721} tokens. If you're writing a contract that needs to receive {ERC721} tokens, you'll want to include this interface. Additionally, {IERC721Receiver} can be used to prevent tokens from becoming forever locked in contracts. Imagine sending an in-game item to an exchange address that can't send it back!. When using <<IERC721-safeTransferFrom,`safeTransferFrom`>>, the token contract checks to see that the receiver is an {IERC721Receiver}, which implies that it knows how to handle {ERC721} tokens. If you're writing a contract that needs to receive {ERC721} tokens, you'll want to include this interface.
Finally, some custom extensions are also included: Finally, some custom extensions are also included:
* {ERC721Mintable} — like the ERC20 version, this allows certain addresses to mint new tokens Additionally there are multiple custom extensions, including:
* {ERC721Pausable} — like the ERC20 version, this allows addresses to freeze transfers of tokens
NOTE: This page is incomplete. We're working to improve it for the next release. Stay tuned! * designation of addresses that can pause token transfers for all users ({ERC721Pausable}).
* destruction of own tokens ({ERC721Burnable}).
== Core == Core
{{IERC721}} {{IERC721}}
{{ERC721}}
{{IERC721Metadata}} {{IERC721Metadata}}
{{ERC721Metadata}}
{{ERC721Enumerable}}
{{IERC721Enumerable}} {{IERC721Enumerable}}
{{IERC721Full}} {{ERC721}}
{{ERC721Full}}
{{IERC721Receiver}} {{IERC721Receiver}}
== Extensions == Extensions
{{ERC721Mintable}} {{ERC721Pausable}}
{{ERC721MetadataMintable}}
{{ERC721Burnable}} {{ERC721Burnable}}
{{ERC721Pausable}}
== Convenience == Convenience
{{ERC721Holder}} {{ERC721Holder}}
...@@ -28,7 +28,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -28,7 +28,7 @@ contract ERC777 is Context, IERC777, IERC20 {
using SafeMath for uint256; using SafeMath for uint256;
using Address for address; using Address for address;
IERC1820Registry constant internal ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); IERC1820Registry constant internal _ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
mapping(address => uint256) private _balances; mapping(address => uint256) private _balances;
...@@ -41,11 +41,11 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -41,11 +41,11 @@ contract ERC777 is Context, IERC777, IERC20 {
// See https://github.com/ethereum/solidity/issues/4024. // See https://github.com/ethereum/solidity/issues/4024.
// keccak256("ERC777TokensSender") // keccak256("ERC777TokensSender")
bytes32 constant private TOKENS_SENDER_INTERFACE_HASH = bytes32 constant private _TOKENS_SENDER_INTERFACE_HASH =
0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895; 0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895;
// keccak256("ERC777TokensRecipient") // keccak256("ERC777TokensRecipient")
bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = bytes32 constant private _TOKENS_RECIPIENT_INTERFACE_HASH =
0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b; 0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b;
// This isn't ever read from - it's only used to respond to the defaultOperators query. // This isn't ever read from - it's only used to respond to the defaultOperators query.
...@@ -78,8 +78,8 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -78,8 +78,8 @@ contract ERC777 is Context, IERC777, IERC20 {
} }
// register interfaces // register interfaces
ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this)); _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this));
ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC20Token"), address(this)); _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC20Token"), address(this));
} }
/** /**
...@@ -440,7 +440,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -440,7 +440,7 @@ contract ERC777 is Context, IERC777, IERC20 {
) )
private private
{ {
address implementer = ERC1820_REGISTRY.getInterfaceImplementer(from, TOKENS_SENDER_INTERFACE_HASH); address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(from, _TOKENS_SENDER_INTERFACE_HASH);
if (implementer != address(0)) { if (implementer != address(0)) {
IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData); IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData);
} }
...@@ -468,7 +468,7 @@ contract ERC777 is Context, IERC777, IERC20 { ...@@ -468,7 +468,7 @@ contract ERC777 is Context, IERC777, IERC20 {
) )
private private
{ {
address implementer = ERC1820_REGISTRY.getInterfaceImplementer(to, TOKENS_RECIPIENT_INTERFACE_HASH); address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH);
if (implementer != address(0)) { if (implementer != address(0)) {
IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData);
} else if (requireReceptionAck) { } else if (requireReceptionAck) {
......
= ERC 777 = ERC 777
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:tokens.adoc#ERC777[ERC777 guide]. 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].
The token behavior itself is implemented in the core contracts: {IERC777}, {ERC777}. The token behavior itself is implemented in the core contracts: {IERC777}, {ERC777}.
......
...@@ -12,22 +12,33 @@ pragma solidity ^0.6.0; ...@@ -12,22 +12,33 @@ pragma solidity ^0.6.0;
library Create2 { library Create2 {
/** /**
* @dev Deploys a contract using `CREATE2`. The address where the contract * @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}. Note that * will be deployed can be known in advance via {computeAddress}.
* a contract cannot be deployed twice using the same salt. *
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/ */
function deploy(bytes32 salt, bytes memory bytecode) internal returns (address) { function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address) {
address addr; address addr;
require(address(this).balance >= amount, "Create2: insufficient balance");
require(bytecode.length != 0, "Create2: bytecode length is zero");
// solhint-disable-next-line no-inline-assembly // solhint-disable-next-line no-inline-assembly
assembly { assembly {
addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt) addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
} }
require(addr != address(0), "Create2: Failed on deploy"); require(addr != address(0), "Create2: Failed on deploy");
return addr; return addr;
} }
/** /**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the `bytecodeHash` * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* or `salt` will result in a new destination address. * `bytecodeHash` or `salt` will result in a new destination address.
*/ */
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) { function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this)); return computeAddress(salt, bytecodeHash, address(this));
......
pragma solidity ^0.6.0;
library EnumerableMap {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Map type with
// bytes32 keys and values.
// The Map implementation uses private functions, and user-facing
// implementations (such as Uint256ToAddressMap) are just wrappers around
// the underlying Map.
// This means that we can only create new EnumerableMaps for types that fit
// in bytes32.
struct MapEntry {
bytes32 _key;
bytes32 _value;
}
struct Map {
// Storage of map keys and values
MapEntry[] _entries;
// Position of the entry defined by a key in the `entries` array, plus 1
// because index 0 means a key is not in the map.
mapping (bytes32 => uint256) _indexes;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function _set(Map storage map, bytes32 key, bytes32 value) private returns (bool) {
// We read and store the key's index to prevent multiple reads from the same storage slot
uint256 keyIndex = map._indexes[key];
if (keyIndex == 0) { // Equivalent to !contains(map, key)
map._entries.push(MapEntry({ _key: key, _value: value }));
// The entry is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
map._indexes[key] = map._entries.length;
return true;
} else {
map._entries[keyIndex - 1]._value = value;
return false;
}
}
/**
* @dev Removes a key-value pair from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function _remove(Map storage map, bytes32 key) private returns (bool) {
// We read and store the key's index to prevent multiple reads from the same storage slot
uint256 keyIndex = map._indexes[key];
if (keyIndex != 0) { // Equivalent to contains(map, key)
// To delete a key-value pair from the _entries array in O(1), we swap the entry to delete with the last one
// in the array, and then remove the last entry (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = keyIndex - 1;
uint256 lastIndex = map._entries.length - 1;
// When the entry to delete is the last one, the swap operation is unnecessary. However, since this occurs
// so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement.
MapEntry storage lastEntry = map._entries[lastIndex];
// Move the last entry to the index where the entry to delete is
map._entries[toDeleteIndex] = lastEntry;
// Update the index for the moved entry
map._indexes[lastEntry._key] = toDeleteIndex + 1; // All indexes are 1-based
// Delete the slot where the moved entry was stored
map._entries.pop();
// Delete the index for the deleted slot
delete map._indexes[key];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function _contains(Map storage map, bytes32 key) private view returns (bool) {
return map._indexes[key] != 0;
}
/**
* @dev Returns the number of key-value pairs in the map. O(1).
*/
function _length(Map storage map) private view returns (uint256) {
return map._entries.length;
}
/**
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
*
* Note that there are no guarantees on the ordering of entries inside the
* array, and it may change when more entries are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Map storage map, uint256 index) private view returns (bytes32, bytes32) {
require(map._entries.length > index, "EnumerableMap: index out of bounds");
MapEntry storage entry = map._entries[index];
return (entry._key, entry._value);
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function _get(Map storage map, bytes32 key) private view returns (bytes32) {
return _get(map, key, "EnumerableMap: nonexistent key");
}
/**
* @dev Same as {_get}, with a custom error message when `key` is not in the map.
*/
function _get(Map storage map, bytes32 key, string memory errorMessage) private view returns (bytes32) {
uint256 keyIndex = map._indexes[key];
require(keyIndex != 0, errorMessage); // Equivalent to contains(map, key)
return map._entries[keyIndex - 1]._value; // All indexes are 1-based
}
// UintToAddressMap
struct UintToAddressMap {
Map _inner;
}
/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) {
return _set(map._inner, bytes32(key), bytes32(uint256(value)));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
return _remove(map._inner, bytes32(key));
}
/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
return _contains(map._inner, bytes32(key));
}
/**
* @dev Returns the number of elements in the map. O(1).
*/
function length(UintToAddressMap storage map) internal view returns (uint256) {
return _length(map._inner);
}
/**
* @dev Returns the element stored at position `index` in the set. O(1).
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
(bytes32 key, bytes32 value) = _at(map._inner, index);
return (uint256(key), address(uint256(value)));
}
/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
return address(uint256(_get(map._inner, bytes32(key))));
}
/**
* @dev Same as {get}, with a custom error message when `key` is not in the map.
*/
function get(UintToAddressMap storage map, uint256 key, string memory errorMessage) internal view returns (address) {
return address(uint256(_get(map._inner, bytes32(key), errorMessage)));
}
}
...@@ -16,6 +16,8 @@ Miscellaneous contracts containing utility functions, often related to working w ...@@ -16,6 +16,8 @@ Miscellaneous contracts containing utility functions, often related to working w
{{EnumerableSet}} {{EnumerableSet}}
{{EnumerableMap}}
{{Create2}} {{Create2}}
{{ReentrancyGuard}} {{ReentrancyGuard}}
......
...@@ -92,4 +92,28 @@ library SafeCast { ...@@ -92,4 +92,28 @@ library SafeCast {
require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits"); require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits");
return uint8(value); return uint8(value);
} }
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
require(value < 2**255, "SafeCast: value doesn't fit in an int256");
return int256(value);
}
} }
{{~#*inline "typed-variable-array"~}} {{~#*inline "typed-variable-array"~}}
{{#each .}}[.var-type\]#{{typeName}}#{{#if name}} [.var-name\]#{{name}}#{{/if}}{{#unless @last}}, {{/unless}}{{/each}} {{#each .}}[.var-type]#{{typeName}}#{{#if name}} [.var-name]#{{name}}#{{/if}}{{#unless @last}}, {{/unless}}{{/each}}
{{~/inline~}} {{~/inline~}}
{{#each linkable}} {{#each linkable}}
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
{{#each ownModifiers}} {{#each ownModifiers}}
[.contract-item] [.contract-item]
[[{{anchor}}]] [[{{anchor}}]]
==== `pass:normal[{{name}}({{> typed-variable-array args}})]` [.item-kind]#modifier# ==== `{{name}}({{> typed-variable-array args}})` [.item-kind]#modifier#
{{natspec.devdoc}} {{natspec.devdoc}}
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
{{#each ownFunctions}} {{#each ownFunctions}}
[.contract-item] [.contract-item]
[[{{anchor}}]] [[{{anchor}}]]
==== `pass:normal[{{name}}({{> typed-variable-array args}}){{#if outputs}}{{> typed-variable-array outputs}}{{/if}}]` [.item-kind]#{{visibility}}# ==== `{{name}}({{> typed-variable-array args}}){{#if outputs}}{{> typed-variable-array outputs}}{{/if}}` [.item-kind]#{{visibility}}#
{{natspec.devdoc}} {{natspec.devdoc}}
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
{{#each ownEvents}} {{#each ownEvents}}
[.contract-item] [.contract-item]
[[{{anchor}}]] [[{{anchor}}]]
==== `pass:normal[{{name}}({{> typed-variable-array args}})]` [.item-kind]#event# ==== `{{name}}({{> typed-variable-array args}})` [.item-kind]#event#
{{natspec.devdoc}} {{natspec.devdoc}}
......
...@@ -11,7 +11,7 @@ OpenZeppelin provides xref:api:access.adoc#Ownable[`Ownable`] for implementing o ...@@ -11,7 +11,7 @@ OpenZeppelin provides xref:api:access.adoc#Ownable[`Ownable`] for implementing o
[source,solidity] [source,solidity]
---- ----
pragma solidity ^0.5.0; pragma solidity ^0.6.0;
import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/access/Ownable.sol";
...@@ -69,7 +69,7 @@ contract MyToken is ERC20, AccessControl { ...@@ -69,7 +69,7 @@ contract MyToken is ERC20, AccessControl {
constructor(address minter) public { constructor(address minter) public {
// Grant the minter role to a specified account // Grant the minter role to a specified account
_grantRole(MINTER_ROLE, minter); _setupRole(MINTER_ROLE, minter);
} }
function mint(address to, uint256 amount) public { function mint(address to, uint256 amount) public {
...@@ -98,8 +98,8 @@ contract MyToken is ERC20, AccessControl { ...@@ -98,8 +98,8 @@ contract MyToken is ERC20, AccessControl {
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
constructor(address minter, address burner) public { constructor(address minter, address burner) public {
_grantRole(MINTER_ROLE, minter); _setupRole(MINTER_ROLE, minter);
_grantRole(BURNER_ROLE, burner); _setupRole(BURNER_ROLE, burner);
} }
function mint(address to, uint256 amount) public { function mint(address to, uint256 amount) public {
...@@ -119,11 +119,11 @@ So clean! By splitting concerns this way, more granular levels of permission may ...@@ -119,11 +119,11 @@ So clean! By splitting concerns this way, more granular levels of permission may
[[granting-and-revoking]] [[granting-and-revoking]]
=== Granting and Revoking Roles === Granting and Revoking Roles
The ERC20 token example above uses `\_grantRole`, an `internal` function that is useful when programmatically asigning roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts? The ERC20 token example above uses `\_setupRole`, an `internal` function that is useful when programmatically asigning roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts?
By default, **accounts with a role cannot grant it or revoke it from other accounts**: all having a role does is making the `hasRole` check pass. To grant and revoke roles dynamically, you will need help from the _role's admin_. By default, **accounts with a role cannot grant it or revoke it from other accounts**: all having a role does is making the `hasRole` check pass. To grant and revoke roles dynamically, you will need help from the _role's admin_.
Every role has an associated admin role, which grants permission to call the `grantRole` and `revokeRole` `external` 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.
...@@ -140,10 +140,10 @@ contract MyToken is ERC20, AccessControl { ...@@ -140,10 +140,10 @@ contract MyToken is ERC20, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
constructor() public { constructor() ERC20("MyToken", "TKN") public {
// Grant the contract deployer the default admin role: it will be able // Grant the contract deployer the default admin role: it will be able
// to grant and revoke any roles // to grant and revoke any roles
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
} }
function mint(address to, uint256 amount) public { function mint(address to, uint256 amount) public {
......
...@@ -55,7 +55,7 @@ As we can see, `_mint` makes it super easy to do this correctly. ...@@ -55,7 +55,7 @@ As we can see, `_mint` makes it super easy to do this correctly.
[[modularizing-the-mechanism]] [[modularizing-the-mechanism]]
== Modularizing the Mechanism == Modularizing the Mechanism
There is one supply mechanism already included in Contracts: xref:api:token/ERC20.adoc#ERC20Mintable[`ERC20Mintable`]. This is a generic mechanism in which a set of accounts is assigned the `minter` role, granting them the permission to call a xref:api:token/ERC20.adoc#ERC20Mintable-mint-address-uint256-[`mint`] function, an external version of `_mint`. There is one supply mechanism already included in Contracts: `ERC20DeployReady`. This is a generic mechanism in which a set of accounts is assigned the `minter` role, granting them the permission to call a `mint` function, an external version of `_mint`.
This can be used for centralized minting, where an externally owned account (i.e. someone with a pair of cryptographic keys) decides how much supply to create and to whom. There are very legitimate use cases for this mechanism, such as https://medium.com/reserve-currency/why-another-stablecoin-866f774afede#3aea[traditional asset-backed stablecoins]. This can be used for centralized minting, where an externally owned account (i.e. someone with a pair of cryptographic keys) decides how much supply to create and to whom. There are very legitimate use cases for this mechanism, such as https://medium.com/reserve-currency/why-another-stablecoin-866f774afede#3aea[traditional asset-backed stablecoins].
...@@ -64,9 +64,9 @@ The accounts with the minter role don't need to be externally owned, though, and ...@@ -64,9 +64,9 @@ The accounts with the minter role don't need to be externally owned, though, and
[source,solidity] [source,solidity]
---- ----
contract MinerRewardMinter { contract MinerRewardMinter {
ERC20Mintable _token; ERC20DeployReady _token;
constructor(ERC20Mintable token) public { constructor(ERC20DeployReady token) public {
_token = token; _token = token;
} }
...@@ -76,7 +76,9 @@ contract MinerRewardMinter { ...@@ -76,7 +76,9 @@ contract MinerRewardMinter {
} }
---- ----
This contract, when initialized with an `ERC20Mintable` instance, will result in exactly the same behavior implemented in the previous section. What is interesting about using `ERC20Mintable` is that we can easily combine multiple supply mechanisms by assigning the role to multiple contracts, and moreover that we can do this dynamically. This contract, when initialized with an `ERC20DeployReady` instance, will result in exactly the same behavior implemented in the previous section. What is interesting about using `ERC20DeployReady` is that we can easily combine multiple supply mechanisms by assigning the role to multiple contracts, and moreover that we can do this dynamically.
TIP: To learn more about roles and permissioned systems, head to our xref:access-control.adoc[Access Control guide].
[[automating-the-reward]] [[automating-the-reward]]
== Automating the Reward == Automating the Reward
......
...@@ -13,19 +13,18 @@ Here's what our GLD token might look like. ...@@ -13,19 +13,18 @@ Here's what our GLD token might look like.
[source,solidity] [source,solidity]
---- ----
pragma solidity ^0.5.0; pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";
contract GLDToken is ERC20, ERC20Detailed { contract GLDToken is ERC20 {
constructor(uint256 initialSupply) ERC20Detailed("Gold", "GLD", 18) public { constructor(uint256 initialSupply) ERC20("Gold", "GLD") public {
_mint(msg.sender, initialSupply); _mint(msg.sender, initialSupply);
} }
} }
---- ----
Our contracts are often used via https://solidity.readthedocs.io/en/latest/contracts.html#inheritance[inheritance], and here we're reusing xref:api:token/ERC20.adoc#erc20[`ERC20`] for the basic standard implementation and xref:api:token/ERC20.adoc#ERC20Detailed[`ERC20Detailed`] to get the xref:api:token/ERC20.adoc#ERC20Detailed-name--[`name`], xref:api:token/ERC20.adoc#ERC20Detailed-symbol--[`symbol`], and xref:api:token/ERC20.adoc#ERC20Detailed-decimals--[`decimals`] properties. Additionally, we're creating an `initialSupply` of tokens, which will be assigned to the address that deploys the contract. Our contracts are often used via https://solidity.readthedocs.io/en/latest/contracts.html#inheritance[inheritance], and here we're reusing xref:api:token/ERC20.adoc#erc20[`ERC20`] for both the basic standard implementation and the xref:api:token/ERC20.adoc#ERC20-name--[`name`], xref:api:token/ERC20.adoc#ERC20-symbol--[`symbol`], and xref:api:token/ERC20.adoc#ERC20-decimals--[`decimals`] optional extensions. Additionally, we're creating an `initialSupply` of tokens, which will be assigned to the address that deploys the contract.
TIP: For a more complete discussion of ERC20 supply mechanisms, see xref:erc20-supply.adoc[Creating ERC20 Supply]. TIP: For a more complete discussion of ERC20 supply mechanisms, see xref:erc20-supply.adoc[Creating ERC20 Supply].
...@@ -53,7 +52,7 @@ We can also xref:api:token/ERC20.adoc#IERC20-transfer-address-uint256-[transfer] ...@@ -53,7 +52,7 @@ We can also xref:api:token/ERC20.adoc#IERC20-transfer-address-uint256-[transfer]
Often, you'll want to be able to divide your tokens into arbitrary amounts: say, if you own `5 GLD`, you may want to send `1.5 GLD` to a friend, and keep `3.5 GLD` to yourself. Unfortunately, Solidity and the EVM do not support this behavior: only integer (whole) numbers can be used, which poses an issue. You may send `1` or `2` tokens, but not `1.5`. Often, you'll want to be able to divide your tokens into arbitrary amounts: say, if you own `5 GLD`, you may want to send `1.5 GLD` to a friend, and keep `3.5 GLD` to yourself. Unfortunately, Solidity and the EVM do not support this behavior: only integer (whole) numbers can be used, which poses an issue. You may send `1` or `2` tokens, but not `1.5`.
To work around this, xref:api:token/ERC20.adoc#ERC20Detailed[`ERC20Detailed`] provides a xref:api:token/ERC20.adoc#ERC20Detailed-decimals--[`decimals`] field, which is used to specify how many decimal places a token has. To be able to transfer `1.5 GLD`, `decimals` must be at least `1`, since that number has a single decimal place. To work around this, xref:api:token/ERC20.adoc#ERC20[`ERC20`] provides a xref:api:token/ERC20.adoc#ERC20-decimals--[`decimals`] field, which is used to specify how many decimal places a token has. To be able to transfer `1.5 GLD`, `decimals` must be at least `1`, since that number has a single decimal place.
How can this be achieved? It's actually very simple: a token contract can use larger integer values, so that a balance of `50` will represent `5 GLD`, a transfer of `15` will correspond to `1.5 GLD` being sent, and so on. How can this be achieved? It's actually very simple: a token contract can use larger integer values, so that a balance of `50` will represent `5 GLD`, a transfer of `15` will correspond to `1.5 GLD` being sent, and so on.
...@@ -61,6 +60,8 @@ It is important to understand that `decimals` is _only used for display purposes ...@@ -61,6 +60,8 @@ It is important to understand that `decimals` is _only used for display purposes
You'll probably want to use a `decimals` value of `18`, just like Ether and most ERC20 token contracts in use, unless you have a very special reason not to. When minting tokens or transferring them around, you will be actually sending the number `num GLD * 10^decimals`. You'll probably want to use a `decimals` value of `18`, just like Ether and most ERC20 token contracts in use, unless you have a very special reason not to. When minting tokens or transferring them around, you will be actually sending the number `num GLD * 10^decimals`.
NOTE: By default, `ERC20` uses a value of `18` for `decimals`. To use a different value, you will need to call xref:api:token/ERC20.adoc#ERC20-_setupDecimals-uint8-[_setupDecimals] in your constructor.
So if you want to send `5` tokens using a token contract with 18 decimals, the the method to call will actually be: So if you want to send `5` tokens using a token contract with 18 decimals, the the method to call will actually be:
```solidity ```solidity
......
...@@ -12,16 +12,16 @@ Here's what a contract for tokenized items might look like: ...@@ -12,16 +12,16 @@ Here's what a contract for tokenized items might look like:
[source,solidity] [source,solidity]
---- ----
pragma solidity ^0.5.0; pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC721/ERC721Full.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/utils/Counters.sol";
contract GameItem is ERC721Full { contract GameItem is ERC721 {
using Counters for Counters.Counter; using Counters for Counters.Counter;
Counters.Counter private _tokenIds; Counters.Counter private _tokenIds;
constructor() ERC721Full("GameItem", "ITM") public { constructor() ERC721("GameItem", "ITM") public {
} }
function awardItem(address player, string memory tokenURI) public returns (uint256) { function awardItem(address player, string memory tokenURI) public returns (uint256) {
...@@ -36,7 +36,7 @@ contract GameItem is ERC721Full { ...@@ -36,7 +36,7 @@ contract GameItem is ERC721Full {
} }
---- ----
The xref:api:token/ERC721.adoc#ERC721Full[`ERC721Full`] contract includes all standard extensions, and is probably the one you want to use. In particular, it includes xref:api:token/ERC721.adoc#ERC721Metadata[`ERC721Metadata`], which provides the xref:api:token/ERC721.adoc#ERC721Metadata-_setTokenURI-uint256-string-[`_setTokenURI`] method we use to store an item's metadata. The xref:api:token/ERC721.adoc#ERC721[`ERC721`] contract includes all standard extensions (xref:api:token/ERC721.adoc#IERC721Metadata[`IERC721Metadata`] and xref:api:token/ERC721.adoc#IERC721Enumerable[`IERC721Enumerable`]). That's where the xref:api:token/ERC721.adoc#ERC721-_setTokenURI-uint256-string-[`_setTokenURI`] method comes from: we use it to store an item's metadata.
Also note that, unlike ERC20, ERC721 lacks a `decimals` field, since each token is distinct and cannot be partitioned. Also note that, unlike ERC20, ERC721 lacks a `decimals` field, since each token is distinct and cannot be partitioned.
......
...@@ -102,17 +102,22 @@ NOTE: Always use `_preRelayedCall` and `_postRelayedCall` functions. Internal ` ...@@ -102,17 +102,22 @@ NOTE: Always use `_preRelayedCall` and `_postRelayedCall` functions. Internal `
=== How to Use `GSNRecipientERC20Fee` === How to Use `GSNRecipientERC20Fee`
Your GSN recipient contract needs to inherit from `GSNRecipientERC20Fee` along with appropriate xref:access-control.adoc[access control] (for token minting), set the token details in the constructor of `GSNRecipientERC20Fee` and create a public `mint` function suitably protected by your chosen access control as per the following sample code (which uses the xref:api:access.adoc#MinterRole[MinterRole]): Your GSN recipient contract needs to inherit from `GSNRecipientERC20Fee` along with appropriate xref:access-control.adoc[access control] (for token minting), set the token details in the constructor of `GSNRecipientERC20Fee` and create a public `mint` function suitably protected by your chosen access control as per the following sample code (which uses xref:api:access.adoc#AccessControl[`AccessControl`]):
[source,solidity] [source,solidity]
---- ----
import "@openzeppelin/contracts/GSN/GSNRecipientERC20Fee"; import "@openzeppelin/contracts/GSN/GSNRecipientERC20Fee";
import "@openzeppelin/contracts/access/AccessControl";
contract MyContract is GSNRecipientERC20Fee, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
contract MyContract is GSNRecipientERC20Fee, MinterRole {
constructor() public GSNRecipientERC20Fee("FeeToken", "FEE") { constructor() public GSNRecipientERC20Fee("FeeToken", "FEE") {
_setupRole(MINTER_ROLE, _msgSender());
} }
function mint(address account, uint256 amount) public onlyMinter { function mint(address account, uint256 amount) public {
require(hasRole(MINTER_ROLE, _msgSender()));
_mint(account, amount); _mint(account, amount);
} }
} }
......
...@@ -26,13 +26,12 @@ Once installed, you can use the contracts in the library by importing them: ...@@ -26,13 +26,12 @@ Once installed, you can use the contracts in the library by importing them:
[source,solidity] [source,solidity]
---- ----
pragma solidity ^0.5.0; pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC721/ERC721Full.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721Mintable.sol";
contract MyNFT is ERC721Full, ERC721Mintable { contract MyNFT is ERC721 {
constructor() ERC721Full("MyNFT", "MNFT") public { constructor() ERC721("MyNFT", "MNFT") public {
} }
} }
---- ----
......
...@@ -28,7 +28,7 @@ Every several months a new major release may come out. These are not scheduled, ...@@ -28,7 +28,7 @@ Every several months a new major release may come out. These are not scheduled,
On the https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.0.0[OpenZeppelin 2.0 release], we committed ourselves to keeping a stable API. We aim to more precisely define what we understand by _stable_ and _API_ here, so users of the library can understand these guarantees and be confident their project won't break unexpectedly. On the https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.0.0[OpenZeppelin 2.0 release], we committed ourselves to keeping a stable API. We aim to more precisely define what we understand by _stable_ and _API_ here, so users of the library can understand these guarantees and be confident their project won't break unexpectedly.
In a nutshell, the API being stable means _if your project is working today, it will continue to do so_. New contracts and features will be added in minor releases, but only in a backwards compatible way. The exception to this rule are contracts in the xref:api:drafts.adoc[Drafts] category, which should be considered unstable. In a nutshell, the API being stable means _if your project is working today, it will continue to do so_. New contracts and features will be added in minor releases, but only in a backwards compatible way.
[[versioning-scheme]] [[versioning-scheme]]
=== Versioning Scheme === Versioning Scheme
...@@ -54,7 +54,7 @@ Finally, sometimes language limitations will force us to e.g. make `internal` a ...@@ -54,7 +54,7 @@ Finally, sometimes language limitations will force us to e.g. make `internal` a
[[libraries]] [[libraries]]
=== Libraries === Libraries
Some of our Solidity libraries use `struct`s to handle internal data that should not be accessed directly (e.g. `Roles`). There's an https://github.com/ethereum/solidity/issues/4637[open issue] in the Solidity repository requesting a language feature to prevent said access, but it looks like it won't be implemented any time soon. Because of this, we will use leading underscores and mark said `struct` s to make it clear to the user that its contents and layout are _not_ part of the API. Some of our Solidity libraries use ``struct``s to handle internal data that should not be accessed directly (e.g. `Roles`). There's an https://github.com/ethereum/solidity/issues/4637[open issue] in the Solidity repository requesting a language feature to prevent said access, but it looks like it won't be implemented any time soon. Because of this, we will use leading underscores and mark said `struct` s to make it clear to the user that its contents and layout are _not_ part of the API.
[[events]] [[events]]
=== Events === Events
......
...@@ -90,11 +90,11 @@ If you want to Escrow some funds, check out xref:api:payment.adoc#Escrow[`Escrow ...@@ -90,11 +90,11 @@ If you want to Escrow some funds, check out xref:api:payment.adoc#Escrow[`Escrow
[[collections]] [[collections]]
== Collections == Collections
If you need support for more powerful collections than Solidity's native arrays and mappings, take a look at xref:api:utils.adoc#EnumerableSet[`EnumerableSet`]. It is similar to a mapping in that it stores and removes elements in constant time and doesn't allow for repeated entries, but it also supports _enumeration_, which means you can easily query all elements of the set both on and off-chain. If you need support for more powerful collections than Solidity's native arrays and mappings, take a look at xref:api:utils.adoc#EnumerableSet[`EnumerableSet`] and xref:api:utils.adoc#EnumerableMap[`EnumerableMap`]. They are similar to mappings in that they store and remove elements in constant time and don't allow for repeated entries, but they also supports _enumeration_, which means you can easily query all stored entries both on and off-chain.
[[misc]] [[misc]]
== Misc == Misc
Want to check if an address is a contract? Use xref:api:utils.adoc#Address[`Address`] and xref:api:utils.adoc#Address-isContract-address-[`Address.isContract()`]. Want to check if an address is a contract? Use xref:api:utils.adoc#Address[`Address`] and xref:api:utils.adoc#Address-isContract-address-[`Address.isContract()`].
Want to keep track of some numbers that increment by 1 every time you want another one? Check out xref:api:drafts.adoc#Counter[`Counter`]. This is useful for lots of things, like creating incremental identifiers, as shown on the xref:721.adoc[ERC721 guide]. Want to keep track of some numbers that increment by 1 every time you want another one? Check out xref:api:utils.adoc#Counters[`Counters`]. This is useful for lots of things, like creating incremental identifiers, as shown on the xref:erc721.adoc[ERC721 guide].
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
}, },
"homepage": "https://openzeppelin.com/contracts/", "homepage": "https://openzeppelin.com/contracts/",
"devDependencies": { "devDependencies": {
"@openzeppelin/cli": "^2.7.2", "@openzeppelin/cli": "^2.8.0",
"@openzeppelin/docs-utils": "^0.1.0", "@openzeppelin/docs-utils": "^0.1.0",
"@openzeppelin/gsn-helpers": "^0.2.3", "@openzeppelin/gsn-helpers": "^0.2.3",
"@openzeppelin/gsn-provider": "^0.1.10", "@openzeppelin/gsn-provider": "^0.1.10",
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
"@openzeppelin/test-helpers": "^0.5.5", "@openzeppelin/test-helpers": "^0.5.5",
"chai": "^4.2.0", "chai": "^4.2.0",
"eslint": "^6.5.1", "eslint": "^6.5.1",
"eslint-config-standard": "^14.1.0", "eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.20.0", "eslint-plugin-import": "^2.20.0",
"eslint-plugin-mocha-no-only": "^1.1.0", "eslint-plugin-mocha-no-only": "^1.1.0",
"eslint-plugin-node": "^10.0.0", "eslint-plugin-node": "^10.0.0",
...@@ -61,11 +61,12 @@ ...@@ -61,11 +61,12 @@
"ethereumjs-util": "^6.2.0", "ethereumjs-util": "^6.2.0",
"ganache-core-coverage": "https://github.com/OpenZeppelin/ganache-core-coverage/releases/download/2.5.3-coverage/ganache-core-coverage-2.5.3.tgz", "ganache-core-coverage": "https://github.com/OpenZeppelin/ganache-core-coverage/releases/download/2.5.3-coverage/ganache-core-coverage-2.5.3.tgz",
"lodash.startcase": "^4.4.0", "lodash.startcase": "^4.4.0",
"lodash.zip": "^4.2.0",
"micromatch": "^4.0.2", "micromatch": "^4.0.2",
"mocha": "^7.1.0", "mocha": "^7.1.1",
"solhint": "^3.0.0-rc.5", "solhint": "^3.0.0-rc.6",
"solidity-coverage": "github:rotcivegaf/solidity-coverage#5875f5b7bc74d447f3312c9c0e9fc7814b482477", "solidity-coverage": "github:rotcivegaf/solidity-coverage#5875f5b7bc74d447f3312c9c0e9fc7814b482477",
"solidity-docgen": "^0.4.0-beta.1" "solidity-docgen": "^0.4.1"
}, },
"dependencies": {} "dependencies": {}
} }
...@@ -11,10 +11,12 @@ const ERC721GSNRecipientMock = contract.fromArtifact('ERC721GSNRecipientMock'); ...@@ -11,10 +11,12 @@ const ERC721GSNRecipientMock = contract.fromArtifact('ERC721GSNRecipientMock');
describe('ERC721GSNRecipient (integration)', function () { describe('ERC721GSNRecipient (integration)', function () {
const [ signer, sender ] = accounts; const [ signer, sender ] = accounts;
const name = 'Non Fungible Token';
const symbol = 'NFT';
const tokenId = '42'; const tokenId = '42';
beforeEach(async function () { beforeEach(async function () {
this.token = await ERC721GSNRecipientMock.new(signer); this.token = await ERC721GSNRecipientMock.new(name, symbol, signer);
}); });
async function testMintToken (token, from, tokenId, options = {}) { async function testMintToken (token, from, tokenId, options = {}) {
......
...@@ -6,7 +6,7 @@ const gsn = require('@openzeppelin/gsn-helpers'); ...@@ -6,7 +6,7 @@ const gsn = require('@openzeppelin/gsn-helpers');
const { expect } = require('chai'); const { expect } = require('chai');
const GSNRecipientERC20FeeMock = contract.fromArtifact('GSNRecipientERC20FeeMock'); const GSNRecipientERC20FeeMock = contract.fromArtifact('GSNRecipientERC20FeeMock');
const ERC20Detailed = contract.fromArtifact('ERC20Detailed'); const ERC20 = contract.fromArtifact('ERC20');
const IRelayHub = contract.fromArtifact('IRelayHub'); const IRelayHub = contract.fromArtifact('IRelayHub');
describe('GSNRecipientERC20Fee', function () { describe('GSNRecipientERC20Fee', function () {
...@@ -17,7 +17,7 @@ describe('GSNRecipientERC20Fee', function () { ...@@ -17,7 +17,7 @@ describe('GSNRecipientERC20Fee', function () {
beforeEach(async function () { beforeEach(async function () {
this.recipient = await GSNRecipientERC20FeeMock.new(name, symbol); this.recipient = await GSNRecipientERC20FeeMock.new(name, symbol);
this.token = await ERC20Detailed.at(await this.recipient.token()); this.token = await ERC20.at(await this.recipient.token());
}); });
describe('token', function () { describe('token', function () {
......
...@@ -17,6 +17,15 @@ describe('AccessControl', function () { ...@@ -17,6 +17,15 @@ describe('AccessControl', function () {
this.accessControl = await AccessControlMock.new({ from: admin }); this.accessControl = await AccessControlMock.new({ from: admin });
}); });
describe('_setupRole', function () {
it('cannot be called outside the constructor', async function () {
await expectRevert(
this.accessControl.setupRole(OTHER_ROLE, other),
'AccessControl: roles cannot be setup after construction'
);
});
});
describe('default admin', function () { describe('default admin', function () {
it('deployer has default admin role', async function () { it('deployer has default admin role', async function () {
expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, admin)).to.equal(true); expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, admin)).to.equal(true);
......
const { expectRevert, constants, expectEvent } = require('@openzeppelin/test-helpers');
const { ZERO_ADDRESS } = constants;
const { expect } = require('chai');
function capitalize (str) {
return str.replace(/\b\w/g, l => l.toUpperCase());
}
// Tests that a role complies with the standard role interface, that is:
// * an onlyRole modifier
// * an isRole function
// * an addRole function, accessible to role havers
// * a renounceRole function
// * roleAdded and roleRemoved events
// Both the modifier and an additional internal remove function are tested through a mock contract that exposes them.
// This mock contract should be stored in this.contract.
//
// @param authorized an account that has the role
// @param otherAuthorized another account that also has the role
// @param other an account that doesn't have the role, passed inside an array for ergonomics
// @param rolename a string with the name of the role
// @param manager undefined for regular roles, or a manager account for managed roles. In these, only the manager
// account can create and remove new role bearers.
function shouldBehaveLikePublicRole (authorized, otherAuthorized, [other], rolename, manager) {
rolename = capitalize(rolename);
describe('should behave like public role', function () {
beforeEach('check preconditions', async function () {
expect(await this.contract[`is${rolename}`](authorized)).to.equal(true);
expect(await this.contract[`is${rolename}`](otherAuthorized)).to.equal(true);
expect(await this.contract[`is${rolename}`](other)).to.equal(false);
});
if (manager === undefined) { // Managed roles are only assigned by the manager, and none are set at construction
it('emits events during construction', async function () {
await expectEvent.inConstruction(this.contract, `${rolename}Added`, {
account: authorized,
});
});
}
it('reverts when querying roles for the null account', async function () {
await expectRevert(this.contract[`is${rolename}`](ZERO_ADDRESS),
'Roles: account is the zero address'
);
});
describe('access control', function () {
context('from authorized account', function () {
const from = authorized;
it('allows access', async function () {
await this.contract[`only${rolename}Mock`]({ from });
});
});
context('from unauthorized account', function () {
const from = other;
it('reverts', async function () {
await expectRevert(this.contract[`only${rolename}Mock`]({ from }),
`${rolename}Role: caller does not have the ${rolename} role`
);
});
});
});
describe('add', function () {
const from = manager === undefined ? authorized : manager;
context(`from ${manager ? 'the manager' : 'a role-haver'} account`, function () {
it('adds role to a new account', async function () {
await this.contract[`add${rolename}`](other, { from });
expect(await this.contract[`is${rolename}`](other)).to.equal(true);
});
it(`emits a ${rolename}Added event`, async function () {
const receipt = await this.contract[`add${rolename}`](other, { from });
expectEvent(receipt, `${rolename}Added`, { account: other });
});
it('reverts when adding role to an already assigned account', async function () {
await expectRevert(this.contract[`add${rolename}`](authorized, { from }),
'Roles: account already has role'
);
});
it('reverts when adding role to the null account', async function () {
await expectRevert(this.contract[`add${rolename}`](ZERO_ADDRESS, { from }),
'Roles: account is the zero address'
);
});
});
});
describe('remove', function () {
// Non-managed roles have no restrictions on the mocked '_remove' function (exposed via 'remove').
const from = manager || other;
context(`from ${manager ? 'the manager' : 'any'} account`, function () {
it('removes role from an already assigned account', async function () {
await this.contract[`remove${rolename}`](authorized, { from });
expect(await this.contract[`is${rolename}`](authorized)).to.equal(false);
expect(await this.contract[`is${rolename}`](otherAuthorized)).to.equal(true);
});
it(`emits a ${rolename}Removed event`, async function () {
const receipt = await this.contract[`remove${rolename}`](authorized, { from });
expectEvent(receipt, `${rolename}Removed`, { account: authorized });
});
it('reverts when removing from an unassigned account', async function () {
await expectRevert(this.contract[`remove${rolename}`](other, { from }),
'Roles: account does not have role'
);
});
it('reverts when removing role from the null account', async function () {
await expectRevert(this.contract[`remove${rolename}`](ZERO_ADDRESS, { from }),
'Roles: account is the zero address'
);
});
});
});
describe('renouncing roles', function () {
it('renounces an assigned role', async function () {
await this.contract[`renounce${rolename}`]({ from: authorized });
expect(await this.contract[`is${rolename}`](authorized)).to.equal(false);
});
it(`emits a ${rolename}Removed event`, async function () {
const receipt = await this.contract[`renounce${rolename}`]({ from: authorized });
expectEvent(receipt, `${rolename}Removed`, { account: authorized });
});
it('reverts when renouncing unassigned role', async function () {
await expectRevert(this.contract[`renounce${rolename}`]({ from: other }),
'Roles: account does not have role'
);
});
});
});
}
module.exports = {
shouldBehaveLikePublicRole,
};
const { accounts, contract, web3 } = require('@openzeppelin/test-environment');
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
const { ZERO_ADDRESS } = constants;
const { expect } = require('chai');
const ERC20MinterPauser = contract.fromArtifact('ERC20MinterPauser');
describe('ERC20MinterPauser', function () {
const [ deployer, other ] = accounts;
const name = 'MinterPauserToken';
const symbol = 'DRT';
const amount = new BN('5000');
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE');
const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE');
beforeEach(async function () {
this.token = await ERC20MinterPauser.new(name, symbol, { from: deployer });
});
it('deployer has the default admin role', async function () {
expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1');
expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer);
});
it('deployer has the minter role', async function () {
expect(await this.token.getRoleMemberCount(MINTER_ROLE)).to.be.bignumber.equal('1');
expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer);
});
it('deployer has the pauser role', async function () {
expect(await this.token.getRoleMemberCount(PAUSER_ROLE)).to.be.bignumber.equal('1');
expect(await this.token.getRoleMember(PAUSER_ROLE, 0)).to.equal(deployer);
});
it('minter and pauser role admin is the default admin', async function () {
expect(await this.token.getRoleAdmin(MINTER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
expect(await this.token.getRoleAdmin(PAUSER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
});
describe('minting', function () {
it('deployer can mint tokens', async function () {
const receipt = await this.token.mint(other, amount, { from: deployer });
expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: other, value: amount });
expect(await this.token.balanceOf(other)).to.be.bignumber.equal(amount);
});
it('other accounts cannot mint tokens', async function () {
await expectRevert(
this.token.mint(other, amount, { from: other }),
'ERC20MinterPauser: must have minter role to mint'
);
});
});
describe('pausing', function () {
it('deployer can pause', async function () {
const receipt = await this.token.pause({ from: deployer });
expectEvent(receipt, 'Paused', { account: deployer });
expect(await this.token.paused()).to.equal(true);
});
it('deployer can unpause', async function () {
await this.token.pause({ from: deployer });
const receipt = await this.token.unpause({ from: deployer });
expectEvent(receipt, 'Unpaused', { account: deployer });
expect(await this.token.paused()).to.equal(false);
});
it('cannot mint while paused', async function () {
await this.token.pause({ from: deployer });
await expectRevert(
this.token.mint(other, amount, { from: deployer }),
'ERC20Pausable: token transfer while paused'
);
});
it('other accounts cannot pause', async function () {
await expectRevert(this.token.pause({ from: other }), 'ERC20MinterPauser: must have pauser role to pause');
});
});
describe('burning', function () {
it('holders can burn their tokens', async function () {
await this.token.mint(other, amount, { from: deployer });
const receipt = await this.token.burn(amount.subn(1), { from: other });
expectEvent(receipt, 'Transfer', { from: other, to: ZERO_ADDRESS, value: amount.subn(1) });
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('1');
});
});
});
const { accounts, contract, web3 } = require('@openzeppelin/test-environment');
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
const { ZERO_ADDRESS } = constants;
const { expect } = require('chai');
const ERC721MinterPauser = contract.fromArtifact('ERC721MinterPauser');
describe('ERC721MinterPauser', function () {
const [ deployer, other ] = accounts;
const name = 'MinterPauserToken';
const symbol = 'DRT';
const tokenId = new BN('1337');
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE');
const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE');
beforeEach(async function () {
this.token = await ERC721MinterPauser.new(name, symbol, { from: deployer });
});
it('deployer has the default admin role', async function () {
expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1');
expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer);
});
it('deployer has the minter role', async function () {
expect(await this.token.getRoleMemberCount(MINTER_ROLE)).to.be.bignumber.equal('1');
expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer);
});
it('deployer has the pauser role', async function () {
expect(await this.token.getRoleMemberCount(PAUSER_ROLE)).to.be.bignumber.equal('1');
expect(await this.token.getRoleMember(PAUSER_ROLE, 0)).to.equal(deployer);
});
it('minter and pauser role admin is the default admin', async function () {
expect(await this.token.getRoleAdmin(MINTER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
expect(await this.token.getRoleAdmin(PAUSER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
});
describe('minting', function () {
it('deployer can mint tokens', async function () {
const receipt = await this.token.mint(other, tokenId, { from: deployer });
expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: other, tokenId });
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('1');
expect(await this.token.ownerOf(tokenId)).to.equal(other);
});
it('other accounts cannot mint tokens', async function () {
await expectRevert(
this.token.mint(other, tokenId, { from: other }),
'ERC721MinterPauser: must have minter role to mint'
);
});
});
describe('pausing', function () {
it('deployer can pause', async function () {
const receipt = await this.token.pause({ from: deployer });
expectEvent(receipt, 'Paused', { account: deployer });
expect(await this.token.paused()).to.equal(true);
});
it('deployer can unpause', async function () {
await this.token.pause({ from: deployer });
const receipt = await this.token.unpause({ from: deployer });
expectEvent(receipt, 'Unpaused', { account: deployer });
expect(await this.token.paused()).to.equal(false);
});
it('cannot mint while paused', async function () {
await this.token.pause({ from: deployer });
await expectRevert(
this.token.mint(other, tokenId, { from: deployer }),
'ERC721Pausable: token transfer while paused'
);
});
it('other accounts cannot pause', async function () {
await expectRevert(this.token.pause({ from: other }), 'ERC721MinterPauser: must have pauser role to pause');
});
});
describe('burning', function () {
it('holders can burn their tokens', async function () {
await this.token.mint(other, tokenId, { from: deployer });
const receipt = await this.token.burn(tokenId, { from: other });
expectEvent(receipt, 'Transfer', { from: other, to: ZERO_ADDRESS, tokenId });
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('0');
expect(await this.token.totalSupply()).to.be.bignumber.equal('0');
});
});
});
...@@ -11,14 +11,45 @@ const { ...@@ -11,14 +11,45 @@ const {
} = require('./ERC20.behavior'); } = require('./ERC20.behavior');
const ERC20Mock = contract.fromArtifact('ERC20Mock'); const ERC20Mock = contract.fromArtifact('ERC20Mock');
const ERC20DecimalsMock = contract.fromArtifact('ERC20DecimalsMock');
describe('ERC20', function () { describe('ERC20', function () {
const [ initialHolder, recipient, anotherAccount ] = accounts; const [ initialHolder, recipient, anotherAccount ] = accounts;
const name = 'My Token';
const symbol = 'MTKN';
const initialSupply = new BN(100); const initialSupply = new BN(100);
beforeEach(async function () { beforeEach(async function () {
this.token = await ERC20Mock.new(initialHolder, initialSupply); this.token = await ERC20Mock.new(name, symbol, initialHolder, initialSupply);
});
it('has a name', async function () {
expect(await this.token.name()).to.equal(name);
});
it('has a symbol', async function () {
expect(await this.token.symbol()).to.equal(symbol);
});
it('has 18 decimals', async function () {
expect(await this.token.decimals()).to.be.bignumber.equal('18');
});
describe('_setupDecimals', function () {
const decimals = new BN(6);
it('can set decimals during construction', async function () {
const token = await ERC20DecimalsMock.new(name, symbol, decimals);
expect(await token.decimals()).to.be.bignumber.equal(decimals);
});
it('reverts if setting decimals after construction', async function () {
const token = await ERC20DecimalsMock.new(name, symbol, decimals);
await expectRevert(token.setupDecimals(decimals.addn(1)), 'ERC20: decimals cannot be changed after construction');
});
}); });
shouldBehaveLikeERC20('ERC20', initialSupply, initialHolder, recipient, anotherAccount); shouldBehaveLikeERC20('ERC20', initialSupply, initialHolder, recipient, anotherAccount);
......
...@@ -10,8 +10,11 @@ describe('ERC20Burnable', function () { ...@@ -10,8 +10,11 @@ describe('ERC20Burnable', function () {
const initialBalance = new BN(1000); const initialBalance = new BN(1000);
const name = 'My Token';
const symbol = 'MTKN';
beforeEach(async function () { beforeEach(async function () {
this.token = await ERC20BurnableMock.new(owner, initialBalance, { from: owner }); this.token = await ERC20BurnableMock.new(name, symbol, owner, initialBalance, { from: owner });
}); });
shouldBehaveLikeERC20Burnable(owner, initialBalance, otherAccounts); shouldBehaveLikeERC20Burnable(owner, initialBalance, otherAccounts);
......
...@@ -10,15 +10,18 @@ describe('ERC20Capped', function () { ...@@ -10,15 +10,18 @@ describe('ERC20Capped', function () {
const cap = ether('1000'); const cap = ether('1000');
const name = 'My Token';
const symbol = 'MTKN';
it('requires a non-zero cap', async function () { it('requires a non-zero cap', async function () {
await expectRevert( await expectRevert(
ERC20Capped.new(new BN(0), { from: minter }), 'ERC20Capped: cap is 0' ERC20Capped.new(name, symbol, new BN(0), { from: minter }), 'ERC20Capped: cap is 0'
); );
}); });
context('once deployed', async function () { context('once deployed', async function () {
beforeEach(async function () { beforeEach(async function () {
this.token = await ERC20Capped.new(cap, { from: minter }); this.token = await ERC20Capped.new(name, symbol, cap, { from: minter });
}); });
shouldBehaveLikeERC20Capped(minter, otherAccounts, cap); shouldBehaveLikeERC20Capped(minter, otherAccounts, cap);
......
const { contract } = require('@openzeppelin/test-environment');
const { BN } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const ERC20DetailedMock = contract.fromArtifact('ERC20DetailedMock');
describe('ERC20Detailed', function () {
const _name = 'My Detailed ERC20';
const _symbol = 'MDT';
const _decimals = new BN(18);
beforeEach(async function () {
this.detailedERC20 = await ERC20DetailedMock.new(_name, _symbol, _decimals);
});
it('has a name', async function () {
expect(await this.detailedERC20.name()).to.equal(_name);
});
it('has a symbol', async function () {
expect(await this.detailedERC20.symbol()).to.equal(_symbol);
});
it('has an amount of decimals', async function () {
expect(await this.detailedERC20.decimals()).to.be.bignumber.equal(_decimals);
});
});
...@@ -11,8 +11,11 @@ describe('ERC20Pausable', function () { ...@@ -11,8 +11,11 @@ describe('ERC20Pausable', function () {
const initialSupply = new BN(100); const initialSupply = new BN(100);
const name = 'My Token';
const symbol = 'MTKN';
beforeEach(async function () { beforeEach(async function () {
this.token = await ERC20PausableMock.new(holder, initialSupply); this.token = await ERC20PausableMock.new(name, symbol, holder, initialSupply);
}); });
describe('pausable token', function () { describe('pausable token', function () {
......
...@@ -10,8 +10,11 @@ describe('ERC20Snapshot', function () { ...@@ -10,8 +10,11 @@ describe('ERC20Snapshot', function () {
const initialSupply = new BN(100); const initialSupply = new BN(100);
const name = 'My Token';
const symbol = 'MTKN';
beforeEach(async function () { beforeEach(async function () {
this.token = await ERC20SnapshotMock.new(initialHolder, initialSupply); this.token = await ERC20SnapshotMock.new(name, symbol, initialHolder, initialSupply);
}); });
describe('snapshot', function () { describe('snapshot', function () {
......
...@@ -10,11 +10,14 @@ const TokenTimelock = contract.fromArtifact('TokenTimelock'); ...@@ -10,11 +10,14 @@ const TokenTimelock = contract.fromArtifact('TokenTimelock');
describe('TokenTimelock', function () { describe('TokenTimelock', function () {
const [ beneficiary ] = accounts; const [ beneficiary ] = accounts;
const name = 'My Token';
const symbol = 'MTKN';
const amount = new BN(100); const amount = new BN(100);
context('with token', function () { context('with token', function () {
beforeEach(async function () { beforeEach(async function () {
this.token = await ERC20Mock.new(beneficiary, 0); // We're not using the preminted tokens this.token = await ERC20Mock.new(name, symbol, beneficiary, 0); // We're not using the preminted tokens
}); });
it('rejects a release time in the past', async function () { it('rejects a release time in the past', async function () {
......
...@@ -14,8 +14,11 @@ describe('ERC721Burnable', function () { ...@@ -14,8 +14,11 @@ describe('ERC721Burnable', function () {
const secondTokenId = new BN(2); const secondTokenId = new BN(2);
const unknownTokenId = new BN(3); const unknownTokenId = new BN(3);
const name = 'Non Fungible Token';
const symbol = 'NFT';
beforeEach(async function () { beforeEach(async function () {
this.token = await ERC721BurnableMock.new(); this.token = await ERC721BurnableMock.new(name, symbol);
}); });
describe('like a burnable ERC721', function () { describe('like a burnable ERC721', function () {
......
const { accounts, contract } = require('@openzeppelin/test-environment');
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const { shouldBehaveLikeERC721 } = require('./ERC721.behavior');
const { shouldSupportInterfaces } = require('../../introspection/SupportsInterface.behavior');
const ERC721FullMock = contract.fromArtifact('ERC721FullMock');
describe('ERC721Full', function () {
const [owner, newOwner, other ] = accounts;
const name = 'Non Fungible Token';
const symbol = 'NFT';
const firstTokenId = new BN(100);
const secondTokenId = new BN(200);
const thirdTokenId = new BN(300);
const nonExistentTokenId = new BN(999);
beforeEach(async function () {
this.token = await ERC721FullMock.new(name, symbol);
});
describe('like a full ERC721', function () {
beforeEach(async function () {
await this.token.mint(owner, firstTokenId);
await this.token.mint(owner, secondTokenId);
});
describe('mint', function () {
beforeEach(async function () {
await this.token.mint(newOwner, thirdTokenId);
});
it('adjusts owner tokens by index', async function () {
expect(await this.token.tokenOfOwnerByIndex(newOwner, 0)).to.be.bignumber.equal(thirdTokenId);
});
it('adjusts all tokens list', async function () {
expect(await this.token.tokenByIndex(2)).to.be.bignumber.equal(thirdTokenId);
});
});
describe('burn', function () {
beforeEach(async function () {
await this.token.burn(firstTokenId, { from: owner });
});
it('removes that token from the token list of the owner', async function () {
expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.equal(secondTokenId);
});
it('adjusts all tokens list', async function () {
expect(await this.token.tokenByIndex(0)).to.be.bignumber.equal(secondTokenId);
});
it('burns all tokens', async function () {
await this.token.burn(secondTokenId, { from: owner });
expect(await this.token.totalSupply()).to.be.bignumber.equal('0');
await expectRevert(
this.token.tokenByIndex(0), 'ERC721Enumerable: global index out of bounds'
);
});
});
describe('metadata', function () {
it('has a name', async function () {
expect(await this.token.name()).to.be.equal(name);
});
it('has a symbol', async function () {
expect(await this.token.symbol()).to.be.equal(symbol);
});
describe('token URI', function () {
const baseURI = 'https://api.com/v1/';
const sampleUri = 'mock://mytoken';
it('it is empty by default', async function () {
expect(await this.token.tokenURI(firstTokenId)).to.be.equal('');
});
it('reverts when queried for non existent token id', async function () {
await expectRevert(
this.token.tokenURI(nonExistentTokenId), 'ERC721Metadata: URI query for nonexistent token'
);
});
it('can be set for a token id', async function () {
await this.token.setTokenURI(firstTokenId, sampleUri);
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(sampleUri);
});
it('reverts when setting for non existent token id', async function () {
await expectRevert(
this.token.setTokenURI(nonExistentTokenId, sampleUri), 'ERC721Metadata: URI set of nonexistent token'
);
});
it('base URI can be set', async function () {
await this.token.setBaseURI(baseURI);
expect(await this.token.baseURI()).to.equal(baseURI);
});
it('base URI is added as a prefix to the token URI', async function () {
await this.token.setBaseURI(baseURI);
await this.token.setTokenURI(firstTokenId, sampleUri);
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + sampleUri);
});
it('token URI can be changed by changing the base URI', async function () {
await this.token.setBaseURI(baseURI);
await this.token.setTokenURI(firstTokenId, sampleUri);
const newBaseURI = 'https://api.com/v2/';
await this.token.setBaseURI(newBaseURI);
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(newBaseURI + sampleUri);
});
it('token URI is empty for tokens with no URI but with base URI', async function () {
await this.token.setBaseURI(baseURI);
expect(await this.token.tokenURI(firstTokenId)).to.be.equal('');
});
it('tokens with URI can be burnt ', async function () {
await this.token.setTokenURI(firstTokenId, sampleUri);
await this.token.burn(firstTokenId, { from: owner });
expect(await this.token.exists(firstTokenId)).to.equal(false);
await expectRevert(
this.token.tokenURI(firstTokenId), 'ERC721Metadata: URI query for nonexistent token'
);
});
});
});
describe('tokensOfOwner', function () {
it('returns total tokens of owner', async function () {
const tokenIds = await this.token.tokensOfOwner(owner);
expect(tokenIds.length).to.equal(2);
expect(tokenIds[0]).to.be.bignumber.equal(firstTokenId);
expect(tokenIds[1]).to.be.bignumber.equal(secondTokenId);
});
});
describe('totalSupply', function () {
it('returns total token supply', async function () {
expect(await this.token.totalSupply()).to.be.bignumber.equal('2');
});
});
describe('tokenOfOwnerByIndex', function () {
describe('when the given index is lower than the amount of tokens owned by the given address', function () {
it('returns the token ID placed at the given index', async function () {
expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.equal(firstTokenId);
});
});
describe('when the index is greater than or equal to the total tokens owned by the given address', function () {
it('reverts', async function () {
await expectRevert(
this.token.tokenOfOwnerByIndex(owner, 2), 'ERC721Enumerable: owner index out of bounds'
);
});
});
describe('when the given address does not own any token', function () {
it('reverts', async function () {
await expectRevert(
this.token.tokenOfOwnerByIndex(other, 0), 'ERC721Enumerable: owner index out of bounds'
);
});
});
describe('after transferring all tokens to another user', function () {
beforeEach(async function () {
await this.token.transferFrom(owner, other, firstTokenId, { from: owner });
await this.token.transferFrom(owner, other, secondTokenId, { from: owner });
});
it('returns correct token IDs for target', async function () {
expect(await this.token.balanceOf(other)).to.be.bignumber.equal('2');
const tokensListed = await Promise.all(
[0, 1].map(i => this.token.tokenOfOwnerByIndex(other, i))
);
expect(tokensListed.map(t => t.toNumber())).to.have.members([firstTokenId.toNumber(),
secondTokenId.toNumber()]);
});
it('returns empty collection for original owner', async function () {
expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('0');
await expectRevert(
this.token.tokenOfOwnerByIndex(owner, 0), 'ERC721Enumerable: owner index out of bounds'
);
});
});
});
describe('tokenByIndex', function () {
it('should return all tokens', async function () {
const tokensListed = await Promise.all(
[0, 1].map(i => this.token.tokenByIndex(i))
);
expect(tokensListed.map(t => t.toNumber())).to.have.members([firstTokenId.toNumber(),
secondTokenId.toNumber()]);
});
it('should revert if index is greater than supply', async function () {
await expectRevert(
this.token.tokenByIndex(2), 'ERC721Enumerable: global index out of bounds'
);
});
[firstTokenId, secondTokenId].forEach(function (tokenId) {
it(`should return all tokens after burning token ${tokenId} and minting new tokens`, async function () {
const newTokenId = new BN(300);
const anotherNewTokenId = new BN(400);
await this.token.burn(tokenId, { from: owner });
await this.token.mint(newOwner, newTokenId);
await this.token.mint(newOwner, anotherNewTokenId);
expect(await this.token.totalSupply()).to.be.bignumber.equal('3');
const tokensListed = await Promise.all(
[0, 1, 2].map(i => this.token.tokenByIndex(i))
);
const expectedTokens = [firstTokenId, secondTokenId, newTokenId, anotherNewTokenId].filter(
x => (x !== tokenId)
);
expect(tokensListed.map(t => t.toNumber())).to.have.members(expectedTokens.map(t => t.toNumber()));
});
});
});
});
shouldBehaveLikeERC721(accounts);
shouldSupportInterfaces([
'ERC165',
'ERC721',
'ERC721Enumerable',
'ERC721Metadata',
]);
});
...@@ -10,8 +10,11 @@ const ERC721Mock = contract.fromArtifact('ERC721Mock'); ...@@ -10,8 +10,11 @@ const ERC721Mock = contract.fromArtifact('ERC721Mock');
describe('ERC721Holder', function () { describe('ERC721Holder', function () {
const [ owner ] = accounts; const [ owner ] = accounts;
const name = 'Non Fungible Token';
const symbol = 'NFT';
it('receives an ERC721 token', async function () { it('receives an ERC721 token', async function () {
const token = await ERC721Mock.new(); const token = await ERC721Mock.new(name, symbol);
const tokenId = new BN(1); const tokenId = new BN(1);
await token.mint(owner, tokenId); await token.mint(owner, tokenId);
......
...@@ -5,19 +5,16 @@ const { ZERO_ADDRESS } = constants; ...@@ -5,19 +5,16 @@ const { ZERO_ADDRESS } = constants;
const { expect } = require('chai'); const { expect } = require('chai');
const { shouldBehaveLikeERC721 } = require('./ERC721.behavior');
const ERC721PausableMock = contract.fromArtifact('ERC721PausableMock'); const ERC721PausableMock = contract.fromArtifact('ERC721PausableMock');
describe('ERC721Pausable', function () { describe('ERC721Pausable', function () {
const [ owner, receiver, operator ] = accounts; const [ owner, receiver, operator ] = accounts;
beforeEach(async function () { const name = 'Non Fungible Token';
this.token = await ERC721PausableMock.new(); const symbol = 'NFT';
});
context('when token is not paused yet', function () { beforeEach(async function () {
shouldBehaveLikeERC721(accounts); this.token = await ERC721PausableMock.new(name, symbol);
}); });
context('when token is paused', function () { context('when token is paused', function () {
......
const { contract, accounts, web3 } = require('@openzeppelin/test-environment'); const { contract, accounts, web3 } = require('@openzeppelin/test-environment');
const { BN, expectRevert } = require('@openzeppelin/test-helpers'); const { balance, BN, ether, expectRevert, send } = require('@openzeppelin/test-helpers');
const { expect } = require('chai'); const { expect } = require('chai');
const Create2Impl = contract.fromArtifact('Create2Impl'); const Create2Impl = contract.fromArtifact('Create2Impl');
const ERC20Mock = contract.fromArtifact('ERC20Mock'); const ERC20Mock = contract.fromArtifact('ERC20Mock');
const ERC20 = contract.fromArtifact('ERC20'); const ERC1820Implementer = contract.fromArtifact('ERC1820Implementer');
describe('Create2', function () { describe('Create2', function () {
const [deployerAccount] = accounts; const [deployerAccount] = accounts;
const salt = 'salt message'; const salt = 'salt message';
const saltHex = web3.utils.soliditySha3(salt); const saltHex = web3.utils.soliditySha3(salt);
const constructorByteCode = `${ERC20Mock.bytecode}${web3.eth.abi
.encodeParameters(['address', 'uint256'], [deployerAccount, 100]).slice(2) const encodedParams = web3.eth.abi.encodeParameters(
}`; ['string', 'string', 'address', 'uint256'],
['MyToken', 'MTKN', deployerAccount, 100]
).slice(2);
const constructorByteCode = `${ERC20Mock.bytecode}${encodedParams}`;
beforeEach(async function () { beforeEach(async function () {
this.factory = await Create2Impl.new(); this.factory = await Create2Impl.new();
...@@ -36,27 +40,53 @@ describe('Create2', function () { ...@@ -36,27 +40,53 @@ describe('Create2', function () {
expect(onChainComputed).to.equal(offChainComputed); expect(onChainComputed).to.equal(offChainComputed);
}); });
it('should deploy a ERC20 from inline assembly code', async function () { it('should deploy a ERC1820Implementer from inline assembly code', async function () {
const offChainComputed = const offChainComputed =
computeCreate2Address(saltHex, ERC20.bytecode, this.factory.address); computeCreate2Address(saltHex, ERC1820Implementer.bytecode, this.factory.address);
await this.factory
.deploy(saltHex, ERC20.bytecode, { from: deployerAccount }); await this.factory.deployERC1820Implementer(0, saltHex);
expect(ERC20.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('should deploy a ERC20Mock with correct balances', async function () {
const offChainComputed = const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address);
computeCreate2Address(saltHex, constructorByteCode, this.factory.address);
await this.factory await this.factory.deploy(0, saltHex, constructorByteCode);
.deploy(saltHex, constructorByteCode, { from: deployerAccount });
const erc20 = await ERC20Mock.at(offChainComputed); const erc20 = await ERC20Mock.at(offChainComputed);
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 () {
const deposit = ether('2');
await send.ether(deployerAccount, this.factory.address, deposit);
expect(await balance.current(this.factory.address)).to.be.bignumber.equal(deposit);
const onChainComputed = await this.factory
.computeAddressWithDeployer(saltHex, web3.utils.keccak256(constructorByteCode), this.factory.address);
await this.factory.deploy(deposit, saltHex, constructorByteCode);
expect(await balance.current(onChainComputed)).to.be.bignumber.equal(deposit);
});
it('should failed deploying a contract in an existent address', async function () { it('should failed deploying a contract in an existent address', async function () {
await this.factory.deploy(saltHex, constructorByteCode, { from: deployerAccount }); await this.factory.deploy(0, saltHex, constructorByteCode, { from: deployerAccount });
await expectRevert(
this.factory.deploy(0, saltHex, constructorByteCode, { from: deployerAccount }), 'Create2: Failed on deploy'
);
});
it('should fail deploying a contract if the bytecode length is zero', async function () {
await expectRevert(
this.factory.deploy(0, saltHex, '0x', { from: deployerAccount }), 'Create2: bytecode length is zero'
);
});
it('should fail deploying a contract if factory contract does not have sufficient balance', async function () {
await expectRevert( await expectRevert(
this.factory.deploy(saltHex, constructorByteCode, { from: deployerAccount }), 'Create2: Failed on deploy' this.factory.deploy(1, saltHex, constructorByteCode, { from: deployerAccount }),
'Create2: insufficient balance'
); );
}); });
}); });
......
const { accounts, contract } = require('@openzeppelin/test-environment');
const { BN, expectEvent } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const zip = require('lodash.zip');
const EnumerableMapMock = contract.fromArtifact('EnumerableMapMock');
describe('EnumerableMap', function () {
const [ accountA, accountB, accountC ] = accounts;
const keyA = new BN('7891');
const keyB = new BN('451');
const keyC = new BN('9592328');
beforeEach(async function () {
this.map = await EnumerableMapMock.new();
});
async function expectMembersMatch (map, keys, values) {
expect(keys.length).to.equal(values.length);
await Promise.all(keys.map(async key =>
expect(await map.contains(key)).to.equal(true)
));
expect(await map.length()).to.bignumber.equal(keys.length.toString());
expect(await Promise.all(keys.map(key =>
map.get(key)
))).to.have.same.members(values);
// To compare key-value pairs, we zip keys and values, and convert BNs to
// strings to workaround Chai limitations when dealing with nested arrays
expect(await Promise.all([...Array(keys.length).keys()].map(async (index) => {
const entry = await map.at(index);
return [entry.key.toString(), entry.value];
}))).to.have.same.deep.members(
zip(keys.map(k => k.toString()), values)
);
}
it('starts empty', async function () {
expect(await this.map.contains(keyA)).to.equal(false);
await expectMembersMatch(this.map, [], []);
});
it('adds a key', async function () {
const receipt = await this.map.set(keyA, accountA);
expectEvent(receipt, 'OperationResult', { result: true });
await expectMembersMatch(this.map, [keyA], [accountA]);
});
it('adds several keys', async function () {
await this.map.set(keyA, accountA);
await this.map.set(keyB, accountB);
await expectMembersMatch(this.map, [keyA, keyB], [accountA, accountB]);
expect(await this.map.contains(keyC)).to.equal(false);
});
it('returns false when adding keys already in the set', async function () {
await this.map.set(keyA, accountA);
const receipt = (await this.map.set(keyA, accountA));
expectEvent(receipt, 'OperationResult', { result: false });
await expectMembersMatch(this.map, [keyA], [accountA]);
});
it('updates values for keys already in the set', async function () {
await this.map.set(keyA, accountA);
await this.map.set(keyA, accountB);
await expectMembersMatch(this.map, [keyA], [accountB]);
});
it('removes added keys', async function () {
await this.map.set(keyA, accountA);
const receipt = await this.map.remove(keyA);
expectEvent(receipt, 'OperationResult', { result: true });
expect(await this.map.contains(keyA)).to.equal(false);
await expectMembersMatch(this.map, [], []);
});
it('returns false when removing keys not in the set', async function () {
const receipt = await this.map.remove(keyA);
expectEvent(receipt, 'OperationResult', { result: false });
expect(await this.map.contains(keyA)).to.equal(false);
});
it('adds and removes multiple keys', async function () {
// []
await this.map.set(keyA, accountA);
await this.map.set(keyC, accountC);
// [A, C]
await this.map.remove(keyA);
await this.map.remove(keyB);
// [C]
await this.map.set(keyB, accountB);
// [C, B]
await this.map.set(keyA, accountA);
await this.map.remove(keyC);
// [A, B]
await this.map.set(keyA, accountA);
await this.map.set(keyB, accountB);
// [A, B]
await this.map.set(keyC, accountC);
await this.map.remove(keyA);
// [B, C]
await this.map.set(keyA, accountA);
await this.map.remove(keyB);
// [A, C]
await expectMembersMatch(this.map, [keyA, keyC], [accountA, accountC]);
expect(await this.map.contains(keyB)).to.equal(false);
});
});
const { accounts, contract } = require('@openzeppelin/test-environment'); const { accounts, contract } = require('@openzeppelin/test-environment');
const { expectEvent } = require('@openzeppelin/test-helpers'); const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
const { expect } = require('chai'); const { expect } = require('chai');
const EnumerableSetMock = contract.fromArtifact('EnumerableSetMock'); const EnumerableSetMock = contract.fromArtifact('EnumerableSetMock');
...@@ -11,18 +11,16 @@ describe('EnumerableSet', function () { ...@@ -11,18 +11,16 @@ describe('EnumerableSet', function () {
this.set = await EnumerableSetMock.new(); this.set = await EnumerableSetMock.new();
}); });
async function expectMembersMatch (set, members) { async function expectMembersMatch (set, values) {
await Promise.all(members.map(async account => await Promise.all(values.map(async account =>
expect(await set.contains(account)).to.equal(true) expect(await set.contains(account)).to.equal(true)
)); ));
expect(await set.enumerate()).to.have.same.members(members); expect(await set.length()).to.bignumber.equal(values.length.toString());
expect(await set.length()).to.bignumber.equal(members.length.toString()); expect(await Promise.all([...Array(values.length).keys()].map(index =>
set.at(index)
expect(await Promise.all([...Array(members.length).keys()].map(index => ))).to.have.same.members(values);
set.get(index)
))).to.have.same.members(members);
} }
it('starts empty', async function () { it('starts empty', async function () {
...@@ -33,7 +31,7 @@ describe('EnumerableSet', function () { ...@@ -33,7 +31,7 @@ describe('EnumerableSet', function () {
it('adds a value', async function () { it('adds a value', async function () {
const receipt = await this.set.add(accountA); const receipt = await this.set.add(accountA);
expectEvent(receipt, 'TransactionResult', { result: true }); expectEvent(receipt, 'OperationResult', { result: true });
await expectMembersMatch(this.set, [accountA]); await expectMembersMatch(this.set, [accountA]);
}); });
...@@ -46,28 +44,32 @@ describe('EnumerableSet', function () { ...@@ -46,28 +44,32 @@ describe('EnumerableSet', function () {
expect(await this.set.contains(accountC)).to.equal(false); expect(await this.set.contains(accountC)).to.equal(false);
}); });
it('returns false when adding elements already in the set', async function () { it('returns false when adding values already in the set', async function () {
await this.set.add(accountA); await this.set.add(accountA);
const receipt = (await this.set.add(accountA)); const receipt = (await this.set.add(accountA));
expectEvent(receipt, 'TransactionResult', { result: false }); expectEvent(receipt, 'OperationResult', { result: false });
await expectMembersMatch(this.set, [accountA]); await expectMembersMatch(this.set, [accountA]);
}); });
it('reverts when retrieving non-existent elements', async function () {
await expectRevert(this.set.at(0), 'EnumerableSet: index out of bounds');
});
it('removes added values', async function () { it('removes added values', async function () {
await this.set.add(accountA); await this.set.add(accountA);
const receipt = await this.set.remove(accountA); const receipt = await this.set.remove(accountA);
expectEvent(receipt, 'TransactionResult', { result: true }); expectEvent(receipt, 'OperationResult', { result: true });
expect(await this.set.contains(accountA)).to.equal(false); expect(await this.set.contains(accountA)).to.equal(false);
await expectMembersMatch(this.set, []); await expectMembersMatch(this.set, []);
}); });
it('returns false when removing elements not in the set', async function () { it('returns false when removing values not in the set', async function () {
const receipt = await this.set.remove(accountA); const receipt = await this.set.remove(accountA);
expectEvent(receipt, 'TransactionResult', { result: false }); expectEvent(receipt, 'OperationResult', { result: false });
expect(await this.set.contains(accountA)).to.equal(false); expect(await this.set.contains(accountA)).to.equal(false);
}); });
......
...@@ -43,4 +43,74 @@ describe('SafeCast', async () => { ...@@ -43,4 +43,74 @@ describe('SafeCast', async () => {
} }
[8, 16, 32, 64, 128].forEach(bits => testToUint(bits)); [8, 16, 32, 64, 128].forEach(bits => testToUint(bits));
describe('toUint256', () => {
const maxInt256 = new BN('2').pow(new BN(255)).subn(1);
const minInt256 = new BN('2').pow(new BN(255)).neg();
const maxUint256 = new BN('2').pow(new BN(256)).subn(1);
it('casts 0', async function () {
expect(await this.safeCast.toUint256(0)).to.be.bignumber.equal('0');
});
it('casts 1', async function () {
expect(await this.safeCast.toUint256(1)).to.be.bignumber.equal('1');
});
it(`casts INT256_MAX (${maxInt256})`, async function () {
expect(await this.safeCast.toUint256(maxInt256)).to.be.bignumber.equal(maxInt256);
});
it('reverts when casting -1', async function () {
await expectRevert(
this.safeCast.toUint256(-1),
'SafeCast: value must be positive'
);
});
it(`reverts when casting INT256_MIN (${minInt256})`, async function () {
await expectRevert(
this.safeCast.toUint256(minInt256),
'SafeCast: value must be positive'
);
});
it(`reverts when casting UINT256_MAX (${maxUint256})`, async function () {
await expectRevert(
this.safeCast.toUint256(maxUint256),
'SafeCast: value must be positive'
);
});
});
describe('toInt256', () => {
const maxUint256 = new BN('2').pow(new BN(256)).subn(1);
const maxInt256 = new BN('2').pow(new BN(255)).subn(1);
it('casts 0', async function () {
expect(await this.safeCast.toInt256(0)).to.be.bignumber.equal('0');
});
it('casts 1', async function () {
expect(await this.safeCast.toInt256(1)).to.be.bignumber.equal('1');
});
it(`casts INT256_MAX (${maxInt256})`, async function () {
expect(await this.safeCast.toInt256(maxInt256)).to.be.bignumber.equal(maxInt256);
});
it(`reverts when casting INT256_MAX + 1 (${maxInt256.addn(1)})`, async function () {
await expectRevert(
this.safeCast.toInt256(maxInt256.addn(1)),
'SafeCast: value doesn\'t fit in an int256'
);
});
it(`reverts when casting UINT256_MAX (${maxUint256})`, async function () {
await expectRevert(
this.safeCast.toInt256(maxUint256),
'SafeCast: value doesn\'t fit in an int256'
);
});
});
}); });
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