Skip to content

Instantly share code, notes, and snippets.

@0xadrii
Created February 14, 2024 07:03
Show Gist options
  • Save 0xadrii/3677f0b5dfb9dcfe6b8b3953115d03f5 to your computer and use it in GitHub Desktop.
Save 0xadrii/3677f0b5dfb9dcfe6b8b3953115d03f5 to your computer and use it in GitHub Desktop.
Mock ERC777 token inspired in OpenZeppelin's implementation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract ERC777Mock {
mapping(address => uint256) private _balances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
// ERC20-allowances
mapping(address => mapping(address => uint256)) private _allowances;
/**
* @dev `defaultOperators` may be an empty array.
*/
constructor(string memory name_, string memory symbol_, uint256 amount) {
_name = name_;
_symbol = symbol_;
_mint(msg.sender, amount, "", "");
}
function mint(address receiver, uint256 amount) public {
_mint(receiver, amount, "", "");
}
/**
* @dev See {IERC777-name}.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev See {IERC777-symbol}.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev See {ERC20-decimals}.
*
* Always returns 18, as per the
* [ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility).
*/
function decimals() public pure virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC777-granularity}.
*
* This implementation always returns `1`.
*/
function granularity() public view virtual returns (uint256) {
return 1;
}
/**
* @dev See {IERC777-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev Returns the amount of tokens owned by an account (`tokenHolder`).
*/
function balanceOf(address tokenHolder) public view virtual returns (uint256) {
return _balances[tokenHolder];
}
/**
* @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) public virtual 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 {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 virtual 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) public virtual 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}, {IERC20-Transfer} and {IERC20-Approval} events.
*/
function transferFrom(address holder, address recipient, uint256 amount) public virtual 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] - 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 {IERC20-Transfer} events.
*
* Requirements
*
* - `account` cannot be the zero address.
* - if `account` is a contract, it must implement the {IERC777Recipient}
* interface.
*/
function _mint(address account, uint256 amount, bytes memory userData, bytes memory operatorData)
internal
virtual
{
require(account != address(0), "ERC777: mint to the zero address");
address operator = msg.sender;
// Update state variables
_totalSupply = _totalSupply + amount;
_balances[account] = _balances[account] + amount;
}
/**
* @dev Burn tokens
* @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 from, uint256 amount, bytes memory data, bytes memory operatorData) internal virtual {
require(from != address(0), "ERC777: burn from the zero address");
address operator = msg.sender;
_callTokensToSend(operator, from, address(0), amount, data, operatorData);
_beforeTokenTransfer(operator, from, address(0), amount);
// Update state variables
_balances[from] = _balances[from] - amount;
_totalSupply = _totalSupply - amount;
}
function _move(
address operator,
address from,
address to,
uint256 amount,
bytes memory userData,
bytes memory operatorData
) private {
_beforeTokenTransfer(operator, from, to, amount);
_balances[from] = _balances[from] - amount;
_balances[to] = _balances[to] + amount;
}
/**
* @dev See {ERC20-_approve}.
*
* Note that accounts cannot have allowance issued by their operators.
*/
function _approve(address holder, address spender, uint256 value) internal {
require(holder != address(0), "ERC777: approve from the zero address");
require(spender != address(0), "ERC777: approve to the zero address");
_allowances[holder][spender] = value;
}
function _callTokensToSend(
address operator,
address from,
address to,
uint256 amount,
bytes memory userData,
bytes memory operatorData
) private {
bytes memory data = abi.encodeWithSignature("tokensToSend(address,address,address,uint256,bytes,bytes)", operator, from, to, amount, userData, operatorData);
from.call(data); // don't check if call was successful
}
function _callTokensReceived(
address operator,
address from,
address to,
uint256 amount,
bytes memory userData,
bytes memory operatorData,
bool
) private {
bytes memory data = abi.encodeWithSignature("tokensReceived(address,address,address,uint256,bytes,bytes)", operator, from, to, amount, userData, operatorData);
to.call(data); // don't check if call was successful
}
/**
* @dev Hook that is called before any token transfer. This includes
* calls to {send}, {transfer}, {operatorSend}, minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be to transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address operator, address from, address to, uint256 amount) internal virtual {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment