Squashed commit of the following:
commit fcf35eb806100de300bd9803ce3150dde1ecc424
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 17 17:16:04 2019 -0300
remove all docsite dependency
commit eeaee9a9d43d70704f6ab17b5126ddbd52b93a50
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 17 17:15:23 2019 -0300
update solidity-docgen
commit f021ff951829ea0c155186749819403c6b76e803
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 17 17:05:06 2019 -0300
update docsite script for new setup
commit ff887699d381cfbbe3acf1f1c0de8e22b58480f3
Merge: c938aa1d 84f85a41
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 17 16:46:46 2019 -0300
Merge branch 'master' into antora
commit c938aa1d9ed05ac83a34e2cebd8353f8331ad6d6
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Tue Jul 16 18:24:29 2019 -0300
make component name shorter
commit 5bbd6931e02cbbd8864c82655ad0f390ceead5f3
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 10 20:16:17 2019 -0300
add all info to docs templates
commit 39682c4515d7cf0f0368ed557f50d2709174208a
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 10 20:13:49 2019 -0300
fix npm docsite script
commit 7ae46bd4a0437abf66150d54d05adf46e3de2cab
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 10 18:48:05 2019 -0300
convert inline docs to asciidoc
commit cfdfd3dee4b4bf582fde22c8cb6e17a603d6e0c8
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 10 17:34:52 2019 -0300
add missing contract names in readmes
commit 15b6a2f9bfb546cf1d3bf4f104278b118bf1b3f4
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 10 17:16:47 2019 -0300
fix script path
commit 80d82b909f9460d1450d401f00b3f309da506b29
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 10 17:13:53 2019 -0300
update version of solidity-docgen
commit a870b6c607b9c2d0012f8a60a4ed1a1c8b7e8ebd
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 10 17:03:53 2019 -0300
add nav generation of api ref
commit 069cff4a25b83752650b54b86d85608c2f547e5e
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Wed Jul 10 16:32:14 2019 -0300
initial migration to asciidoc and new docgen version
commit 55216eed0a6551da913c8d1da4b2a0d0d3faa1a8
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Tue Jun 25 20:39:35 2019 -0300
add basic api doc example
commit 0cbe50ce2173b6d1d9a698329d91220f58822a53
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Tue Jun 25 19:31:31 2019 -0300
add sidebars
commit 256fc942845307258ac9dc25aace48117fa10f79
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Tue Jun 25 15:22:38 2019 -0300
add page titles
commit f4d0effa70e1fc0662729863e8ee72a8821bc458
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Tue Jun 25 15:19:41 2019 -0300
add contracts index file
commit b73b06359979f7d933df7f2b283c50cb1c31b2a0
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Tue Jun 25 15:14:52 2019 -0300
fix header levels
commit fb57d9b820f09a1b7c04eed1a205be0e45866cac
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Tue Jun 25 15:11:47 2019 -0300
switch format to preferred asciidoctor format
commit 032181d8804137332c71534753929d080a31a71f
Author: Francisco Giordano <frangio.1@gmail.com>
Date: Tue Jun 25 15:05:38 2019 -0300
initialize antora component and convert docs to asciidoc
475 lines
16 KiB
Solidity
475 lines
16 KiB
Solidity
pragma solidity ^0.5.0;
|
|
|
|
import "./IERC777.sol";
|
|
import "./IERC777Recipient.sol";
|
|
import "./IERC777Sender.sol";
|
|
import "../../token/ERC20/IERC20.sol";
|
|
import "../../math/SafeMath.sol";
|
|
import "../../utils/Address.sol";
|
|
import "../../introspection/IERC1820Registry.sol";
|
|
|
|
/**
|
|
* @dev Implementation of the {IERC777} interface.
|
|
*
|
|
* This implementation is agnostic to the way tokens are created. This means
|
|
* that a supply mechanism has to be added in a derived contract using {_mint}.
|
|
*
|
|
* Support for ERC20 is included in this contract, as specified by the EIP: both
|
|
* the ERC777 and ERC20 interfaces can be safely used when interacting with it.
|
|
* Both {IERC777-Sent} and {IERC20-Transfer} events are emitted on token
|
|
* movements.
|
|
*
|
|
* Additionally, the {IERC777-granularity} value is hard-coded to `1`, meaning that there
|
|
* are no special restrictions in the amount of tokens that created, moved, or
|
|
* destroyed. This makes integration with ERC20 applications seamless.
|
|
*/
|
|
contract ERC777 is IERC777, IERC20 {
|
|
using SafeMath for uint256;
|
|
using Address for address;
|
|
|
|
IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
|
|
|
|
mapping(address => uint256) private _balances;
|
|
|
|
uint256 private _totalSupply;
|
|
|
|
string private _name;
|
|
string private _symbol;
|
|
|
|
// We inline the result of the following hashes because Solidity doesn't resolve them at compile time.
|
|
// See https://github.com/ethereum/solidity/issues/4024.
|
|
|
|
// keccak256("ERC777TokensSender")
|
|
bytes32 constant private TOKENS_SENDER_INTERFACE_HASH =
|
|
0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895;
|
|
|
|
// keccak256("ERC777TokensRecipient")
|
|
bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH =
|
|
0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b;
|
|
|
|
// This isn't ever read from - it's only used to respond to the defaultOperators query.
|
|
address[] private _defaultOperatorsArray;
|
|
|
|
// Immutable, but accounts may revoke them (tracked in __revokedDefaultOperators).
|
|
mapping(address => bool) private _defaultOperators;
|
|
|
|
// For each account, a mapping of its operators and revoked default operators.
|
|
mapping(address => mapping(address => bool)) private _operators;
|
|
mapping(address => mapping(address => bool)) private _revokedDefaultOperators;
|
|
|
|
// ERC20-allowances
|
|
mapping (address => mapping (address => uint256)) private _allowances;
|
|
|
|
/**
|
|
* @dev `defaultOperators` may be an empty array.
|
|
*/
|
|
constructor(
|
|
string memory name,
|
|
string memory symbol,
|
|
address[] memory defaultOperators
|
|
) public {
|
|
_name = name;
|
|
_symbol = symbol;
|
|
|
|
_defaultOperatorsArray = defaultOperators;
|
|
for (uint256 i = 0; i < _defaultOperatorsArray.length; i++) {
|
|
_defaultOperators[_defaultOperatorsArray[i]] = true;
|
|
}
|
|
|
|
// register interfaces
|
|
_erc1820.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this));
|
|
_erc1820.setInterfaceImplementer(address(this), keccak256("ERC20Token"), address(this));
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-name}.
|
|
*/
|
|
function name() public view returns (string memory) {
|
|
return _name;
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-symbol}.
|
|
*/
|
|
function symbol() public view returns (string memory) {
|
|
return _symbol;
|
|
}
|
|
|
|
/**
|
|
* @dev See {ERC20Detailed-decimals}.
|
|
*
|
|
* Always returns 18, as per the
|
|
* [ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility).
|
|
*/
|
|
function decimals() public pure returns (uint8) {
|
|
return 18;
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-granularity}.
|
|
*
|
|
* This implementation always returns `1`.
|
|
*/
|
|
function granularity() public view returns (uint256) {
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-totalSupply}.
|
|
*/
|
|
function totalSupply() public view returns (uint256) {
|
|
return _totalSupply;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the amount of tokens owned by an account (`tokenHolder`).
|
|
*/
|
|
function balanceOf(address tokenHolder) public view returns (uint256) {
|
|
return _balances[tokenHolder];
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-send}.
|
|
*
|
|
* Also emits a {Transfer} event for ERC20 compatibility.
|
|
*/
|
|
function send(address recipient, uint256 amount, bytes calldata data) external {
|
|
_send(msg.sender, msg.sender, recipient, amount, data, "", true);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC20-transfer}.
|
|
*
|
|
* Unlike `send`, `recipient` is _not_ required to implement the {IERC777Recipient}
|
|
* interface if it is a contract.
|
|
*
|
|
* Also emits a {Sent} event.
|
|
*/
|
|
function transfer(address recipient, uint256 amount) external returns (bool) {
|
|
require(recipient != address(0), "ERC777: transfer to the zero address");
|
|
|
|
address from = msg.sender;
|
|
|
|
_callTokensToSend(from, from, recipient, amount, "", "");
|
|
|
|
_move(from, from, recipient, amount, "", "");
|
|
|
|
_callTokensReceived(from, from, recipient, amount, "", "", false);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-burn}.
|
|
*
|
|
* Also emits a {Transfer} event for ERC20 compatibility.
|
|
*/
|
|
function burn(uint256 amount, bytes calldata data) external {
|
|
_burn(msg.sender, msg.sender, amount, data, "");
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-isOperatorFor}.
|
|
*/
|
|
function isOperatorFor(
|
|
address operator,
|
|
address tokenHolder
|
|
) public view returns (bool) {
|
|
return operator == tokenHolder ||
|
|
(_defaultOperators[operator] && !_revokedDefaultOperators[tokenHolder][operator]) ||
|
|
_operators[tokenHolder][operator];
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-authorizeOperator}.
|
|
*/
|
|
function authorizeOperator(address operator) external {
|
|
require(msg.sender != operator, "ERC777: authorizing self as operator");
|
|
|
|
if (_defaultOperators[operator]) {
|
|
delete _revokedDefaultOperators[msg.sender][operator];
|
|
} else {
|
|
_operators[msg.sender][operator] = true;
|
|
}
|
|
|
|
emit AuthorizedOperator(operator, msg.sender);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-revokeOperator}.
|
|
*/
|
|
function revokeOperator(address operator) external {
|
|
require(operator != msg.sender, "ERC777: revoking self as operator");
|
|
|
|
if (_defaultOperators[operator]) {
|
|
_revokedDefaultOperators[msg.sender][operator] = true;
|
|
} else {
|
|
delete _operators[msg.sender][operator];
|
|
}
|
|
|
|
emit RevokedOperator(operator, msg.sender);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-defaultOperators}.
|
|
*/
|
|
function defaultOperators() public view returns (address[] memory) {
|
|
return _defaultOperatorsArray;
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-operatorSend}.
|
|
*
|
|
* Emits {Sent} and {Transfer} events.
|
|
*/
|
|
function operatorSend(
|
|
address sender,
|
|
address recipient,
|
|
uint256 amount,
|
|
bytes calldata data,
|
|
bytes calldata operatorData
|
|
)
|
|
external
|
|
{
|
|
require(isOperatorFor(msg.sender, sender), "ERC777: caller is not an operator for holder");
|
|
_send(msg.sender, sender, recipient, amount, data, operatorData, true);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC777-operatorBurn}.
|
|
*
|
|
* Emits {Burned} and {Transfer} events.
|
|
*/
|
|
function operatorBurn(address account, uint256 amount, bytes calldata data, bytes calldata operatorData) external {
|
|
require(isOperatorFor(msg.sender, account), "ERC777: caller is not an operator for holder");
|
|
_burn(msg.sender, account, amount, data, operatorData);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC20-allowance}.
|
|
*
|
|
* Note that operator and allowance concepts are orthogonal: operators may
|
|
* not have allowance, and accounts with allowance may not be operators
|
|
* themselves.
|
|
*/
|
|
function allowance(address holder, address spender) public view returns (uint256) {
|
|
return _allowances[holder][spender];
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC20-approve}.
|
|
*
|
|
* Note that accounts cannot have allowance issued by their operators.
|
|
*/
|
|
function approve(address spender, uint256 value) external returns (bool) {
|
|
address holder = msg.sender;
|
|
_approve(holder, spender, value);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC20-transferFrom}.
|
|
*
|
|
* Note that operator and allowance concepts are orthogonal: operators cannot
|
|
* call `transferFrom` (unless they have allowance), and accounts with
|
|
* allowance cannot call `operatorSend` (unless they are operators).
|
|
*
|
|
* Emits {Sent}, {Transfer} and {Approval} events.
|
|
*/
|
|
function transferFrom(address holder, address recipient, uint256 amount) external returns (bool) {
|
|
require(recipient != address(0), "ERC777: transfer to the zero address");
|
|
require(holder != address(0), "ERC777: transfer from the zero address");
|
|
|
|
address spender = msg.sender;
|
|
|
|
_callTokensToSend(spender, holder, recipient, amount, "", "");
|
|
|
|
_move(spender, holder, recipient, amount, "", "");
|
|
_approve(holder, spender, _allowances[holder][spender].sub(amount));
|
|
|
|
_callTokensReceived(spender, holder, recipient, amount, "", "", false);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @dev Creates `amount` tokens and assigns them to `account`, increasing
|
|
* the total supply.
|
|
*
|
|
* If a send hook is registered for `account`, the corresponding function
|
|
* will be called with `operator`, `data` and `operatorData`.
|
|
*
|
|
* See {IERC777Sender} and {IERC777Recipient}.
|
|
*
|
|
* Emits {Minted} and {Transfer} events.
|
|
*
|
|
* Requirements
|
|
*
|
|
* - `account` cannot be the zero address.
|
|
* - if `account` is a contract, it must implement the {IERC777Recipient}
|
|
* interface.
|
|
*/
|
|
function _mint(
|
|
address operator,
|
|
address account,
|
|
uint256 amount,
|
|
bytes memory userData,
|
|
bytes memory operatorData
|
|
)
|
|
internal
|
|
{
|
|
require(account != address(0), "ERC777: mint to the zero address");
|
|
|
|
// Update state variables
|
|
_totalSupply = _totalSupply.add(amount);
|
|
_balances[account] = _balances[account].add(amount);
|
|
|
|
_callTokensReceived(operator, address(0), account, amount, userData, operatorData, true);
|
|
|
|
emit Minted(operator, account, amount, userData, operatorData);
|
|
emit Transfer(address(0), account, amount);
|
|
}
|
|
|
|
/**
|
|
* @dev Send tokens
|
|
* @param operator address operator requesting the transfer
|
|
* @param from address token holder address
|
|
* @param to address recipient address
|
|
* @param amount uint256 amount of tokens to transfer
|
|
* @param userData bytes extra information provided by the token holder (if any)
|
|
* @param operatorData bytes extra information provided by the operator (if any)
|
|
* @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient
|
|
*/
|
|
function _send(
|
|
address operator,
|
|
address from,
|
|
address to,
|
|
uint256 amount,
|
|
bytes memory userData,
|
|
bytes memory operatorData,
|
|
bool requireReceptionAck
|
|
)
|
|
private
|
|
{
|
|
require(from != address(0), "ERC777: send from the zero address");
|
|
require(to != address(0), "ERC777: send to the zero address");
|
|
|
|
_callTokensToSend(operator, from, to, amount, userData, operatorData);
|
|
|
|
_move(operator, from, to, amount, userData, operatorData);
|
|
|
|
_callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck);
|
|
}
|
|
|
|
/**
|
|
* @dev Burn tokens
|
|
* @param operator address operator requesting the operation
|
|
* @param from address token holder address
|
|
* @param amount uint256 amount of tokens to burn
|
|
* @param data bytes extra information provided by the token holder
|
|
* @param operatorData bytes extra information provided by the operator (if any)
|
|
*/
|
|
function _burn(
|
|
address operator,
|
|
address from,
|
|
uint256 amount,
|
|
bytes memory data,
|
|
bytes memory operatorData
|
|
)
|
|
private
|
|
{
|
|
require(from != address(0), "ERC777: burn from the zero address");
|
|
|
|
_callTokensToSend(operator, from, address(0), amount, data, operatorData);
|
|
|
|
// Update state variables
|
|
_totalSupply = _totalSupply.sub(amount);
|
|
_balances[from] = _balances[from].sub(amount);
|
|
|
|
emit Burned(operator, from, amount, data, operatorData);
|
|
emit Transfer(from, address(0), amount);
|
|
}
|
|
|
|
function _move(
|
|
address operator,
|
|
address from,
|
|
address to,
|
|
uint256 amount,
|
|
bytes memory userData,
|
|
bytes memory operatorData
|
|
)
|
|
private
|
|
{
|
|
_balances[from] = _balances[from].sub(amount);
|
|
_balances[to] = _balances[to].add(amount);
|
|
|
|
emit Sent(operator, from, to, amount, userData, operatorData);
|
|
emit Transfer(from, to, amount);
|
|
}
|
|
|
|
function _approve(address holder, address spender, uint256 value) private {
|
|
// TODO: restore this require statement if this function becomes internal, or is called at a new callsite. It is
|
|
// currently unnecessary.
|
|
//require(holder != address(0), "ERC777: approve from the zero address");
|
|
require(spender != address(0), "ERC777: approve to the zero address");
|
|
|
|
_allowances[holder][spender] = value;
|
|
emit Approval(holder, spender, value);
|
|
}
|
|
|
|
/**
|
|
* @dev Call from.tokensToSend() if the interface is registered
|
|
* @param operator address operator requesting the transfer
|
|
* @param from address token holder address
|
|
* @param to address recipient address
|
|
* @param amount uint256 amount of tokens to transfer
|
|
* @param userData bytes extra information provided by the token holder (if any)
|
|
* @param operatorData bytes extra information provided by the operator (if any)
|
|
*/
|
|
function _callTokensToSend(
|
|
address operator,
|
|
address from,
|
|
address to,
|
|
uint256 amount,
|
|
bytes memory userData,
|
|
bytes memory operatorData
|
|
)
|
|
private
|
|
{
|
|
address implementer = _erc1820.getInterfaceImplementer(from, TOKENS_SENDER_INTERFACE_HASH);
|
|
if (implementer != address(0)) {
|
|
IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Call to.tokensReceived() if the interface is registered. Reverts if the recipient is a contract but
|
|
* tokensReceived() was not registered for the recipient
|
|
* @param operator address operator requesting the transfer
|
|
* @param from address token holder address
|
|
* @param to address recipient address
|
|
* @param amount uint256 amount of tokens to transfer
|
|
* @param userData bytes extra information provided by the token holder (if any)
|
|
* @param operatorData bytes extra information provided by the operator (if any)
|
|
* @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient
|
|
*/
|
|
function _callTokensReceived(
|
|
address operator,
|
|
address from,
|
|
address to,
|
|
uint256 amount,
|
|
bytes memory userData,
|
|
bytes memory operatorData,
|
|
bool requireReceptionAck
|
|
)
|
|
private
|
|
{
|
|
address implementer = _erc1820.getInterfaceImplementer(to, TOKENS_RECIPIENT_INTERFACE_HASH);
|
|
if (implementer != address(0)) {
|
|
IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData);
|
|
} else if (requireReceptionAck) {
|
|
require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient");
|
|
}
|
|
}
|
|
}
|