-
Notifications
You must be signed in to change notification settings - Fork 11.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* IERC777 from specs, constants returned, up to defaultOperators. (#1159) * IERC777 oprarator approvals (#1159) * ERC777 oprarator approvals fixes and tests * IERC777 send and receive with ERC820 (#1159) * ERC777 Add burn functions and fix send functions (#1159) * ERC777 Make expectEvent compatible with web3.js 1.0 (#1159) * ERC777 Add ERC820 deploy script (#1159) * ERC777 Complete implementation of ERC777 (#1159) This implementation conforms to the current EIP * ERC777 Update ERC820 Registry contract to final version (#1159) * ERC777 Move contracts to 'drafts' folder (#1159) * ERC777: Update to ERC1820 registry and linter error fix (#1159) * ERC777: implement recent changes of EIP777 (#1159) * ERC777 Fix formatting (#1159) * ERC777 Update to solc 0.5.2 (#1159) * ERC777 Fix travis CI errors (#1159) * ERC777 Fix linter errors again... (#1159) * ERC777 Fix unit test (#1159) * ERC777 Fix unit test again (#1159) * Remove extra newlines. * Rename ERC777Base to ERC777. * Remove 'Token' from contract names. * Replace ops for operators. * Move operator check out of _send. * Remove ERC777Burnable. * Remove ERC1820Client, now using the interface directly. * Minor internal refactors in contracts. * Delete extra test helpers. * Simplified tests. * Add basic 777 tests. * Add granularity send test. * Add first operator send tests. * Add burn tests. * Refactor send and burn tests. * Improve send burn refactor. * Greatly improve test module. * Burn instead of send removed tokens. * Add operator tests. * Improve send tests under changing operators. * Refactor and merge send and burn tests. * Add missing and not-implemented tests. * Make _burn private. * Fix typo. * Greatly improve tokensToSend tests. * Refactor hook tests. * Fix hook tests. * Update openzeppelin-test-helpers and ERC1820 address. * Fix natspec indentation. * Make interface functions external. * Remove redundant private revoke and authorize functions. * Improved readability of if statement. * Remove unnecessary asserts. * Add non-one granularity test. * Fix hook call order in _mint. * Fix _mint not reverting on failure to implement tokensReceived. * Remove special case in operatorFn when from is 0. * Refactor ERC777SenderMock. * Add tokensReceived tests. * switch to updated ganache-cli-coverage fork * Fix linter errors. * Add mint tests. * Fix linter errors. * Fix tests. * Update test/drafts/ERC777/ERC777.test.js Co-Authored-By: nventuro <[email protected]> * Add changelog entry.
- Loading branch information
Showing
11 changed files
with
3,689 additions
and
5,644 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,357 @@ | ||
pragma solidity ^0.5.2; | ||
|
||
import "./IERC777.sol"; | ||
import "./IERC777Recipient.sol"; | ||
import "./IERC777Sender.sol"; | ||
import "../../math/SafeMath.sol"; | ||
import "../../utils/Address.sol"; | ||
import "../IERC1820Registry.sol"; | ||
|
||
/** | ||
* @title ERC777 token implementation | ||
* @author etsvigun <[email protected]>, Bertrand Masius <[email protected]> | ||
*/ | ||
contract ERC777 is IERC777 { | ||
using SafeMath for uint256; | ||
using Address for address; | ||
|
||
IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); | ||
|
||
string private _name; | ||
|
||
string private _symbol; | ||
|
||
mapping(address => uint256) private _balances; | ||
|
||
uint256 private _totalSupply; | ||
|
||
uint256 private _granularity; | ||
|
||
bytes32 constant private TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender"); | ||
bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); | ||
|
||
// 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; | ||
|
||
constructor( | ||
string memory name, | ||
string memory symbol, | ||
uint256 granularity, | ||
address[] memory defaultOperators | ||
) public { | ||
require(granularity > 0); | ||
|
||
_name = name; | ||
_symbol = symbol; | ||
_granularity = granularity; | ||
|
||
_defaultOperatorsArray = defaultOperators; | ||
for (uint256 i = 0; i < _defaultOperatorsArray.length; i++) { | ||
_defaultOperators[_defaultOperatorsArray[i]] = true; | ||
} | ||
|
||
// register interface | ||
_erc1820.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this)); | ||
} | ||
|
||
/** | ||
* @dev Send the amount of tokens from the address msg.sender to the address to | ||
* @param to address recipient address | ||
* @param amount uint256 amount of tokens to transfer | ||
* @param data bytes information attached to the send, and intended for the recipient (to) | ||
*/ | ||
function send(address to, uint256 amount, bytes calldata data) external { | ||
_send(msg.sender, msg.sender, to, amount, data, ""); | ||
} | ||
|
||
/** | ||
* @dev Send the amount of tokens on behalf of the address from to the address to | ||
* @param from address token holder address. | ||
* @param to address recipient address | ||
* @param amount uint256 amount of tokens to transfer | ||
* @param data bytes information attached to the send, and intended for the recipient (to) | ||
* @param operatorData bytes extra information provided by the operator (if any) | ||
*/ | ||
function operatorSend( | ||
address from, | ||
address to, | ||
uint256 amount, | ||
bytes calldata data, | ||
bytes calldata operatorData | ||
) | ||
external | ||
{ | ||
require(isOperatorFor(msg.sender, from)); | ||
_send(msg.sender, from, to, amount, data, operatorData); | ||
} | ||
|
||
/** | ||
* @dev Burn the amount of tokens from the address msg.sender | ||
* @param amount uint256 amount of tokens to transfer | ||
* @param data bytes extra information provided by the token holder | ||
*/ | ||
function burn(uint256 amount, bytes calldata data) external { | ||
_burn(msg.sender, msg.sender, amount, data, ""); | ||
} | ||
|
||
/** | ||
* @dev Burn the amount of tokens on behalf of the address from | ||
* @param from address token holder address. | ||
* @param amount uint256 amount of tokens to transfer | ||
* @param data bytes extra information provided by the token holder | ||
* @param operatorData bytes extra information provided by the operator (if any) | ||
*/ | ||
function operatorBurn(address from, uint256 amount, bytes calldata data, bytes calldata operatorData) external { | ||
require(isOperatorFor(msg.sender, from)); | ||
_burn(msg.sender, from, amount, data, operatorData); | ||
} | ||
|
||
/** | ||
* @dev Authorize an operator for the sender | ||
* @param operator address to be authorized as operator | ||
*/ | ||
function authorizeOperator(address operator) external { | ||
require(msg.sender != operator); | ||
|
||
if (_defaultOperators[operator]) { | ||
delete _revokedDefaultOperators[msg.sender][operator]; | ||
} else { | ||
_operators[msg.sender][operator] = true; | ||
} | ||
|
||
emit AuthorizedOperator(operator, msg.sender); | ||
} | ||
|
||
/** | ||
* @dev Revoke operator rights from one of the default operators | ||
* @param operator address to revoke operator rights from | ||
*/ | ||
function revokeOperator(address operator) external { | ||
require(operator != msg.sender); | ||
|
||
if (_defaultOperators[operator]) { | ||
_revokedDefaultOperators[msg.sender][operator] = true; | ||
} else { | ||
delete _operators[msg.sender][operator]; | ||
} | ||
|
||
emit RevokedOperator(operator, msg.sender); | ||
} | ||
|
||
/** | ||
* @return the name of the token. | ||
*/ | ||
function name() public view returns (string memory) { | ||
return _name; | ||
} | ||
|
||
/** | ||
* @return the symbol of the token. | ||
*/ | ||
function symbol() public view returns (string memory) { | ||
return _symbol; | ||
} | ||
|
||
/** | ||
* @dev Total number of tokens in existence | ||
*/ | ||
function totalSupply() public view returns (uint256) { | ||
return _totalSupply; | ||
} | ||
|
||
/** | ||
* @dev Gets the balance of the specified address. | ||
* @param tokenHolder The address to query the balance of. | ||
* @return uint256 representing the amount owned by the specified address. | ||
*/ | ||
function balanceOf(address tokenHolder) public view returns (uint256) { | ||
return _balances[tokenHolder]; | ||
} | ||
|
||
/** | ||
* @dev Gets the token's granularity, | ||
* i.e. the smallest number of tokens (in the basic unit) | ||
* which may be minted, sent or burned at any time | ||
* @return uint256 granularity | ||
*/ | ||
function granularity() public view returns (uint256) { | ||
return _granularity; | ||
} | ||
|
||
/** | ||
* @dev Get the list of default operators as defined by the token contract. | ||
* @return address[] default operators | ||
*/ | ||
function defaultOperators() public view returns (address[] memory) { | ||
return _defaultOperatorsArray; | ||
} | ||
|
||
/** | ||
* @dev Indicate whether an address | ||
* is an operator of the tokenHolder address | ||
* @param operator address which may be an operator of tokenHolder | ||
* @param tokenHolder address of a token holder which may have the operator | ||
* address as an operator. | ||
*/ | ||
function isOperatorFor( | ||
address operator, | ||
address tokenHolder | ||
) public view returns (bool) { | ||
return operator == tokenHolder || | ||
(_defaultOperators[operator] && !_revokedDefaultOperators[tokenHolder][operator]) || | ||
_operators[tokenHolder][operator]; | ||
} | ||
|
||
/** | ||
* @dev Mint tokens. Does not check authorization of operator | ||
* @dev the caller may ckeck that operator is authorized before calling | ||
* @param operator address operator requesting the operation | ||
* @param to address token recipient address | ||
* @param amount uint256 amount of tokens to mint | ||
* @param userData bytes extra information defined by the token recipient (if any) | ||
* @param operatorData bytes extra information provided by the operator (if any) | ||
*/ | ||
function _mint( | ||
address operator, | ||
address to, | ||
uint256 amount, | ||
bytes memory userData, | ||
bytes memory operatorData | ||
) | ||
internal | ||
{ | ||
require(to != address(0)); | ||
require((amount % _granularity) == 0); | ||
|
||
// Update state variables | ||
_totalSupply = _totalSupply.add(amount); | ||
_balances[to] = _balances[to].add(amount); | ||
|
||
_callTokensReceived(operator, address(0), to, amount, userData, operatorData); | ||
|
||
emit Minted(operator, to, amount, userData, operatorData); | ||
} | ||
|
||
/** | ||
* @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) | ||
*/ | ||
function _send( | ||
address operator, | ||
address from, | ||
address to, | ||
uint256 amount, | ||
bytes memory userData, | ||
bytes memory operatorData | ||
) | ||
private | ||
{ | ||
require(from != address(0)); | ||
require(to != address(0)); | ||
require((amount % _granularity) == 0); | ||
|
||
_callTokensToSend(operator, from, to, amount, userData, operatorData); | ||
|
||
// Update state variables | ||
_balances[from] = _balances[from].sub(amount); | ||
_balances[to] = _balances[to].add(amount); | ||
|
||
_callTokensReceived(operator, from, to, amount, userData, operatorData); | ||
|
||
emit Sent(operator, from, to, amount, userData, operatorData); | ||
} | ||
|
||
/** | ||
* @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)); | ||
require((amount % _granularity) == 0); | ||
|
||
_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); | ||
} | ||
|
||
/** | ||
* @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) | ||
*/ | ||
function _callTokensReceived( | ||
address operator, | ||
address from, | ||
address to, | ||
uint256 amount, | ||
bytes memory userData, | ||
bytes memory operatorData | ||
) | ||
private | ||
{ | ||
address implementer = _erc1820.getInterfaceImplementer(to, TOKENS_RECIPIENT_INTERFACE_HASH); | ||
if (implementer != address(0)) { | ||
IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); | ||
} else { | ||
require(!to.isContract()); | ||
} | ||
} | ||
} |
Oops, something went wrong.