Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ERC20 compatibility to ERC777. #1735

Merged
merged 17 commits into from
May 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### New features:
* `ERC1820`: added support for interacting with the [ERC1820](https://eips.ethereum.org/EIPS/eip-1820) registry contract (`IERC1820Registry`), as well as base contracts that can be registered as implementers there. ([#1677](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1677))
* `ERC777`: initial support for the [ERC777 token](https://eips.ethereum.org/EIPS/eip-777), which has multiple improvements over `ERC20` such as built-in burning, a more straightforward permission system, and optional sender and receiver hooks on transfer (mandatory for contracts!). ([#1684](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1684))
* `ERC777`: support for the [ERC777 token](https://eips.ethereum.org/EIPS/eip-777), which has multiple improvements over `ERC20` (but is backwards compatible with it) such as built-in burning, a more straightforward permission system, and optional sender and receiver hooks on transfer (mandatory for contracts!). ([#1684](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1684))
* All contracts now have revert reason strings, which give insight into error conditions, and help debug failing transactions. ([#1704](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1704))

### Improvements:
Expand Down
172 changes: 140 additions & 32 deletions contracts/drafts/ERC777/ERC777.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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 "../IERC1820Registry.sol";
Expand All @@ -11,20 +12,19 @@ import "../IERC1820Registry.sol";
* @title ERC777 token implementation, with granularity harcoded to 1.
* @author etsvigun <utgarda@gmail.com>, Bertrand Masius <github@catageeks.tk>
*/
contract ERC777 is IERC777 {
contract ERC777 is IERC777, IERC20 {
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;

string private _name;
string private _symbol;

bytes32 constant private TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender");
bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient");

Expand All @@ -38,6 +38,9 @@ contract ERC777 is IERC777 {
mapping(address => mapping(address => bool)) private _operators;
mapping(address => mapping(address => bool)) private _revokedDefaultOperators;

// ERC20-allowances
mapping (address => mapping (address => uint256)) private _allowances;

constructor(
string memory name,
string memory symbol,
Expand All @@ -51,8 +54,9 @@ contract ERC777 is IERC777 {
_defaultOperators[_defaultOperatorsArray[i]] = true;
}

// register interface
// register interfaces
_erc1820.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this));
_erc1820.setInterfaceImplementer(address(this), keccak256("ERC20Token"), address(this));
}

/**
Expand All @@ -62,7 +66,7 @@ contract ERC777 is IERC777 {
* @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, "");
_sendRequiringReceptionAck(msg.sender, msg.sender, to, amount, data, "");
}

/**
Expand All @@ -82,8 +86,36 @@ contract ERC777 is IERC777 {
)
external
{
require(isOperatorFor(msg.sender, from));
_send(msg.sender, from, to, amount, data, operatorData);
require(isOperatorFor(msg.sender, from), "ERC777: caller is not an operator for holder");
_sendRequiringReceptionAck(msg.sender, from, to, amount, data, operatorData);
}

/**
* @dev Transfer token to a specified address.
* Required for ERC20 compatiblity. Note that transferring tokens this way may result in locked tokens (i.e. tokens
* can be sent to a contract that does not implement the ERC777TokensRecipient interface).
* @param to The address to transfer to.
* @param value The amount to be transferred.
*/
function transfer(address to, uint256 value) external returns (bool) {
_transfer(msg.sender, msg.sender, to, value);
return true;
}

/**
* @dev Transfer tokens from one address to another.
* Note that while this function emits an Approval event, this is not required as per the specification,
* and other compliant implementations may not emit the event.
* Required for ERC20 compatiblity. Note that transferring tokens this way may result in locked tokens (i.e. tokens
* can be sent to a contract that does not implement the ERC777TokensRecipient interface).
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param value uint256 the amount of tokens to be transferred
*/
function transferFrom(address from, address to, uint256 value) external returns (bool) {
_transfer(msg.sender, from, to, value);
_approve(from, msg.sender, _allowances[from][msg.sender].sub(value));
return true;
}

/**
Expand All @@ -103,7 +135,7 @@ contract ERC777 is IERC777 {
* @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));
require(isOperatorFor(msg.sender, from), "ERC777: caller is not an operator for holder");
_burn(msg.sender, from, amount, data, operatorData);
}

Expand All @@ -112,7 +144,7 @@ contract ERC777 is IERC777 {
* @param operator address to be authorized as operator
*/
function authorizeOperator(address operator) external {
require(msg.sender != operator);
require(msg.sender != operator, "ERC777: authorizing self as operator");

if (_defaultOperators[operator]) {
delete _revokedDefaultOperators[msg.sender][operator];
Expand All @@ -128,7 +160,7 @@ contract ERC777 is IERC777 {
* @param operator address to revoke operator rights from
*/
function revokeOperator(address operator) external {
require(operator != msg.sender);
require(operator != msg.sender, "ERC777: revoking self as operator");

if (_defaultOperators[operator]) {
_revokedDefaultOperators[msg.sender][operator] = true;
Expand All @@ -140,17 +172,18 @@ contract ERC777 is IERC777 {
}

/**
* @return the name of the token.
*/
function name() public view returns (string memory) {
return _name;
}

/**
* @return the symbol of the token.
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* Beware that changing an allowance with this method brings the risk that someone may use both the old
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
* Required for ERC20 compatilibity.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
*/
function symbol() public view returns (string memory) {
return _symbol;
function approve(address spender, uint256 value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}

/**
Expand All @@ -169,6 +202,27 @@ contract ERC777 is IERC777 {
return _balances[tokenHolder];
}

/**
* @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;
}

/**
* @return the number of decimals of the token.
*/
function decimals() public pure returns (uint8) {
return 18; // The spec requires that decimals be 18
}

/**
* @dev Gets the token's granularity,
* i.e. the smallest number of tokens (in the basic unit)
Expand Down Expand Up @@ -203,6 +257,17 @@ contract ERC777 is IERC777 {
_operators[tokenHolder][operator];
}

/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
* Required for ERC20 compatibility.
* @param owner address The address which owns the funds.
* @param spender address The address which will spend the funds.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}

/**
* @dev Mint tokens. Does not check authorization of operator
* @dev the caller may ckeck that operator is authorized before calling
Expand All @@ -221,15 +286,42 @@ contract ERC777 is IERC777 {
)
internal
{
require(to != address(0));
require(to != address(0), "ERC777: mint to the zero address");

// Update state variables
_totalSupply = _totalSupply.add(amount);
_balances[to] = _balances[to].add(amount);

_callTokensReceived(operator, address(0), to, amount, userData, operatorData);
_callTokensReceived(operator, address(0), to, amount, userData, operatorData, true);

emit Minted(operator, to, amount, userData, operatorData);
emit Transfer(address(0), to, amount);
}

function _transfer(address operator, address from, address to, uint256 amount) private {
_sendAllowingNoReceptionAck(operator, from, to, amount, "", "");
}

function _sendRequiringReceptionAck(
address operator,
address from,
address to,
uint256 amount,
bytes memory userData,
bytes memory operatorData
) private {
_send(operator, from, to, amount, userData, operatorData, true);
}

function _sendAllowingNoReceptionAck(
address operator,
address from,
address to,
uint256 amount,
bytes memory userData,
bytes memory operatorData
) private {
_send(operator, from, to, amount, userData, operatorData, false);
}

/**
Expand All @@ -240,29 +332,32 @@ contract ERC777 is IERC777 {
* @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
bytes memory operatorData,
bool requireReceptionAck
)
private
{
require(from != address(0));
require(to != address(0));
require(from != address(0), "ERC777: transfer from the zero address");
require(to != address(0), "ERC777: transfer to the zero address");

_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);
_callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck);

emit Sent(operator, from, to, amount, userData, operatorData);
emit Transfer(from, to, amount);
}

/**
Expand All @@ -282,7 +377,7 @@ contract ERC777 is IERC777 {
)
private
{
require(from != address(0));
require(from != address(0), "ERC777: burn from the zero address");

_callTokensToSend(operator, from, address(0), amount, data, operatorData);

Expand All @@ -291,6 +386,17 @@ contract ERC777 is IERC777 {
_balances[from] = _balances[from].sub(amount);

emit Burned(operator, from, amount, data, operatorData);
emit Transfer(from, address(0), amount);
}

function _approve(address owner, 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(owner != address(0), "ERC777: approve from the zero address");
require(spender != address(0), "ERC777: approve to the zero address");

_allowances[owner][spender] = value;
emit Approval(owner, spender, value);
}

/**
Expand Down Expand Up @@ -327,22 +433,24 @@ contract ERC777 is IERC777 {
* @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
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 {
require(!to.isContract());
} else if (requireReceptionAck) {
require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient");
}
}
}
Loading