Skip to content

Instantly share code, notes, and snippets.

@gorbunovperm
Created October 15, 2024 08:49
Show Gist options
  • Save gorbunovperm/19c9175737bc9ffd246fa235fe2840ac to your computer and use it in GitHub Desktop.
Save gorbunovperm/19c9175737bc9ffd246fa235fe2840ac to your computer and use it in GitHub Desktop.
Files for erc7417 audit
// SPDX-License-Identifier: GPL-3.0
pragma solidity =0.8.19;
library Address {
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
}
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
interface IERC20Metadata is IERC20 {
/// @return The name of the token
function name() external view returns (string memory);
/// @return The symbol of the token
function symbol() external view returns (string memory);
/// @return The number of decimal places the token has
function decimals() external view returns (uint8);
}
abstract contract IERC223Recipient {
function tokenReceived(address _from, uint _value, bytes memory _data) public virtual returns (bytes4)
{
return 0x8943ec02;
}
}
abstract contract ERC165 {
/*
* bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
*/
bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
mapping(bytes4 => bool) private _supportedInterfaces;
constructor () {
// Derived contracts need only register support for their own interfaces,
// we register support for ERC165 itself here
_registerInterface(_INTERFACE_ID_ERC165);
}
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return _supportedInterfaces[interfaceId];
}
function _registerInterface(bytes4 interfaceId) internal virtual {
require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
_supportedInterfaces[interfaceId] = true;
}
}
abstract contract IERC223 {
function name() public view virtual returns (string memory);
function symbol() public view virtual returns (string memory);
function decimals() public view virtual returns (uint8);
function totalSupply() public view virtual returns (uint256);
function balanceOf(address who) public virtual view returns (uint);
function transfer(address to, uint value) public virtual returns (bool success);
function transfer(address to, uint value, bytes calldata data) public payable virtual returns (bool success);
event Transfer(address indexed from, address indexed to, uint value, bytes data);
}
interface standardERC20
{
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC223WrapperToken {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function standard() external view returns (string memory);
function origin() external view returns (address);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external payable returns (bool);
function transfer(address to, uint256 value, bytes calldata data) external payable returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function mint(address _recipient, uint256 _quantity) external;
function burn(address _recipient, uint256 _quantity) external;
}
interface IERC20WrapperToken {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function standard() external view returns (string memory);
function origin() external view returns (address);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function mint(address _recipient, uint256 _quantity) external;
function burn(address _recipient, uint256 _quantity) external;
}
contract ERC20Rescue
{
// ERC-20 tokens can get stuck on a contracts balance due to lack of error handling.
//
// The author of the ERC-7417 can extract ERC-20 tokens if they are mistakenly sent
// to the wrapper-contracts balance.
// Contact [email protected]
address public extractor = 0x01000B5fE61411C466b70631d7fF070187179Bbf;
function safeTransfer(address token, address to, uint value) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
}
function rescueERC20(address _token, uint256 _amount) external
{
safeTransfer(_token, extractor, _amount);
}
}
contract ERC223WrapperToken is IERC223, ERC165, ERC20Rescue
{
address public creator = msg.sender;
address private wrapper_for;
mapping(address account => mapping(address spender => uint256)) private allowances;
event Transfer(address indexed from, address indexed to, uint256 amount);
event TransferData(bytes data);
event Approval(address indexed owner, address indexed spender, uint256 amount);
function set(address _wrapper_for) external
{
require(msg.sender == creator);
wrapper_for = _wrapper_for;
}
uint256 private _totalSupply;
mapping(address => uint256) private balances; // List of user balances.
function totalSupply() public view override returns (uint256) { return _totalSupply; }
function balanceOf(address _owner) public view override returns (uint256) { return balances[_owner]; }
/**
* @dev The ERC165 introspection function.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(IERC20).interfaceId ||
interfaceId == type(standardERC20).interfaceId ||
interfaceId == type(IERC223WrapperToken).interfaceId ||
interfaceId == type(IERC223).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev Standard ERC-223 transfer function.
* Calls _to if it is a contract. Does not transfer tokens to contracts
* which do not explicitly declare the tokenReceived function.
* @param _to - transfer recipient. Can be contract or EOA.
* @param _value - the quantity of tokens to transfer.
* @param _data - metadata to send alongside the transaction. Can be used to encode subsequent calls in the recipient.
*/
function transfer(address _to, uint _value, bytes calldata _data) public payable override returns (bool success)
{
balances[msg.sender] = balances[msg.sender] - _value;
balances[_to] = balances[_to] + _value;
if(Address.isContract(_to)) {
IERC223Recipient(_to).tokenReceived(msg.sender, _value, _data);
}
if (msg.value > 0) payable(_to).transfer(msg.value);
emit Transfer(msg.sender, _to, _value, _data);
emit Transfer(msg.sender, _to, _value); // Old ERC-20 compatible event. Added for backwards compatibility reasons.
return true;
}
/**
* @dev Standard ERC-223 transfer function without _data parameter. It is supported for
* backwards compatibility with ERC-20 services.
* Calls _to if it is a contract. Does not transfer tokens to contracts
* which do not explicitly declare the tokenReceived function.
* @param _to - transfer recipient. Can be contract or EOA.
* @param _value - the quantity of tokens to transfer.
*/
function transfer(address _to, uint _value) public override returns (bool success)
{
bytes memory _empty = hex"00000000";
balances[msg.sender] = balances[msg.sender] - _value;
balances[_to] = balances[_to] + _value;
if(Address.isContract(_to)) {
IERC223Recipient(_to).tokenReceived(msg.sender, _value, _empty);
}
emit Transfer(msg.sender, _to, _value, _empty);
emit Transfer(msg.sender, _to, _value); // Old ERC-20 compatible event. Added for backwards compatibility reasons.
return true;
}
function name() public view override returns (string memory) { return IERC20Metadata(wrapper_for).name(); }
function symbol() public view override returns (string memory) { return string.concat(IERC20Metadata(wrapper_for).symbol(), "223"); }
function decimals() public view override returns (uint8) { return IERC20Metadata(wrapper_for).decimals(); }
function standard() public pure returns (uint32) { return 223; }
function origin() public view returns (address) { return wrapper_for; }
/**
* @dev Minting function which will only be called by the converter contract.
* @param _recipient - the address which will receive tokens.
* @param _quantity - the number of tokens to create.
*/
function mint(address _recipient, uint256 _quantity) external
{
require(msg.sender == creator, "Wrapper Token: Only the creator contract can mint wrapper tokens.");
balances[_recipient] += _quantity;
_totalSupply += _quantity;
}
/**
* @dev Burning function which will only be called by the converter contract.
* @param _quantity - the number of tokens to destroy. TokenConverter can only destroy tokens on it's own address.
* Only the token converter is allowed to burn wrapper-tokens.
*/
function burn(uint256 _quantity) external
{
require(msg.sender == creator, "Wrapper Token: Only the creator contract can destroy wrapper tokens.");
balances[msg.sender] -= _quantity;
_totalSupply -= _quantity;
}
// ERC-20 functions for backwards compatibility.
function allowance(address owner, address spender) public view virtual returns (uint256) {
return allowances[owner][spender];
}
function approve(address _spender, uint _value) public returns (bool) {
// Safety checks.
require(_spender != address(0), "ERC-223: Spender error.");
allowances[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function transferFrom(address _from, address _to, uint _value) public returns (bool) {
require(allowances[_from][msg.sender] >= _value, "ERC-223: Insufficient allowance.");
balances[_from] -= _value;
allowances[_from][msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(_from, _to, _value);
return true;
}
}
contract ERC20WrapperToken is IERC20, ERC165, ERC20Rescue
{
address public creator = msg.sender;
address public wrapper_for;
mapping(address account => mapping(address spender => uint256)) private allowances;
function set(address _wrapper_for) external
{
require(msg.sender == creator);
wrapper_for = _wrapper_for;
}
uint256 private _totalSupply;
mapping(address => uint256) private balances; // List of user balances.
function balanceOf(address _owner) public view override returns (uint256) { return balances[_owner]; }
function name() public view returns (string memory) { return IERC20Metadata(wrapper_for).name(); }
function symbol() public view returns (string memory) { return string.concat(IERC223(wrapper_for).name(), "20"); }
function decimals() public view returns (uint8) { return IERC20Metadata(wrapper_for).decimals(); }
function totalSupply() public view override returns (uint256) { return _totalSupply; }
function origin() public view returns (address) { return wrapper_for; }
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(IERC20).interfaceId ||
interfaceId == type(IERC20WrapperToken).interfaceId ||
super.supportsInterface(interfaceId);
}
function transfer(address _to, uint _value) public override returns (bool success)
{
balances[msg.sender] = balances[msg.sender] - _value;
balances[_to] = balances[_to] + _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
function mint(address _recipient, uint256 _quantity) external
{
require(msg.sender == creator, "Wrapper Token: Only the creator contract can mint wrapper tokens.");
balances[_recipient] += _quantity;
_totalSupply += _quantity;
}
function burn(address _from, uint256 _quantity) external
{
require(msg.sender == creator, "Wrapper Token: Only the creator contract can destroy wrapper tokens.");
balances[_from] -= _quantity;
_totalSupply -= _quantity;
}
function allowance(address owner, address spender) public view virtual returns (uint256) {
return allowances[owner][spender];
}
function approve(address _spender, uint _value) public returns (bool) {
// Safety checks.
require(_spender != address(0), "ERC-20: Spender error.");
allowances[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function transferFrom(address _from, address _to, uint _value) public returns (bool) {
require(allowances[_from][msg.sender] >= _value, "ERC-20: Insufficient allowance.");
balances[_from] -= _value;
allowances[_from][msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(_from, _to, _value);
return true;
}
}
contract TokenStandardConverter is IERC223Recipient
{
event ERC223WrapperCreated(address indexed _token, address indexed _ERC223Wrapper);
event ERC20WrapperCreated(address indexed _token, address indexed _ERC20Wrapper);
mapping (address => ERC223WrapperToken) public erc223Wrappers; // A list of token wrappers. First one is ERC-20 origin, second one is ERC-223 version.
mapping (address => ERC20WrapperToken) public erc20Wrappers;
mapping (address => address) public erc223Origins;
mapping (address => address) public erc20Origins;
mapping (address => uint256) public erc20Supply; // Token => how much was deposited.
function getERC20WrapperFor(address _token) public view returns (address)
{
return address(erc20Wrappers[_token]);
}
function getERC223WrapperFor(address _token) public view returns (address)
{
return address(erc223Wrappers[_token]);
}
function getERC20OriginFor(address _token) public view returns (address)
{
return (address(erc20Origins[_token]));
}
function getERC223OriginFor(address _token) public view returns (address)
{
return (address(erc223Origins[_token]));
}
function predictWrapperAddress(address _token,
bool _isERC20 // Is the provided _token a ERC-20 or not?
// If it is set as ERC-20 then we will predict the address of a
// ERC-223 wrapper for that token.
// Otherwise we will predict ERC-20 wrapper address.
) view external returns (address)
{
bytes memory _bytecode;
if(_isERC20)
{
_bytecode= type(ERC223WrapperToken).creationCode;
}
else
{
_bytecode= type(ERC20WrapperToken).creationCode;
}
bytes32 hash = keccak256(
abi.encodePacked(
bytes1(0xff), address(this), keccak256(abi.encode(_token)), keccak256(_bytecode)
)
);
return address(uint160(uint(hash)));
}
function tokenReceived(address _from, uint _value, bytes memory /* _data */) public override returns (bytes4)
{
require(erc223Origins[msg.sender] == address(0), "Error: creating wrapper for a wrapper token.");
// There are two possible cases:
// 1. A user deposited ERC-223 origin token to convert it to ERC-20 wrapper
// 2. A user deposited ERC-223 wrapper token to unwrap it to ERC-20 origin.
if(erc20Origins[msg.sender] != address(0))
{
// Origin for deposited token exists.
// Unwrap ERC-223 wrapper.
erc20Supply[erc20Origins[msg.sender]] -= _value;
safeTransfer(erc20Origins[msg.sender], _from, _value);
ERC223WrapperToken(msg.sender).burn(_value);
return this.tokenReceived.selector;
}
// Otherwise origin for the sender token doesn't exist
// There are two possible cases:
// 1. ERC-20 wrapper for the deposited token exists
// 2. ERC-20 wrapper for the deposited token doesn't exist and must be created.
else if(address(erc20Wrappers[msg.sender]) == address(0))
{
// Create ERC-20 wrapper if it doesn't exist.
createERC20Wrapper(msg.sender);
}
// Mint ERC-20 wrapper tokens for the deposited ERC-223 token
// if the ERC-20 wrapper didn't exist then it was just created in the above statement.
erc20Wrappers[msg.sender].mint(_from, _value);
return this.tokenReceived.selector;
}
function createERC223Wrapper(address _token) public returns (address)
{
require(address(erc223Wrappers[_token]) == address(0), "ERROR: Wrapper exists");
require(!isWrapper(_token), "Error: Creating wrapper for a wrapper token");
ERC223WrapperToken _newERC223Wrapper = new ERC223WrapperToken{salt: keccak256(abi.encode(_token))}();
_newERC223Wrapper.set(_token);
erc223Wrappers[_token] = _newERC223Wrapper;
erc20Origins[address(_newERC223Wrapper)] = _token;
emit ERC223WrapperCreated(_token, address(_newERC223Wrapper));
return address(_newERC223Wrapper);
}
function createERC20Wrapper(address _token) public returns (address)
{
require(address(erc20Wrappers[_token]) == address(0), "ERROR: Wrapper already exists.");
require(!isWrapper(_token), "Error: Creating wrapper for a wrapper token");
ERC20WrapperToken _newERC20Wrapper = new ERC20WrapperToken{salt: keccak256(abi.encode(_token))}();
_newERC20Wrapper.set(_token);
erc20Wrappers[_token] = _newERC20Wrapper;
erc223Origins[address(_newERC20Wrapper)] = _token;
emit ERC20WrapperCreated(_token, address(_newERC20Wrapper));
return address(_newERC20Wrapper);
}
function wrapERC20toERC223(address _ERC20token, uint256 _amount) public returns (bool)
{
// If there is no active wrapper for a token that user wants to wrap
// then create it.
if(address(erc223Wrappers[_ERC20token]) == address(0))
{
createERC223Wrapper(_ERC20token);
}
uint256 _converterBalance = IERC20(_ERC20token).balanceOf(address(this)); // Safety variable.
safeTransferFrom(_ERC20token, msg.sender, address(this), _amount);
_amount = IERC20(_ERC20token).balanceOf(address(this)) - _converterBalance;
erc20Supply[_ERC20token] += _amount;
erc223Wrappers[_ERC20token].mint(msg.sender, _amount);
return true;
}
function unwrapERC20toERC223(address _ERC20token, uint256 _amount) public returns (bool)
{
require(IERC20(_ERC20token).balanceOf(msg.sender) >= _amount, "Error: Insufficient balance.");
require(erc223Origins[_ERC20token] != address(0), "Error: provided token is not a ERC-20 wrapper.");
ERC20WrapperToken(_ERC20token).burn(msg.sender, _amount);
safeTransfer(erc223Origins[_ERC20token], msg.sender, _amount);
return true;
}
function convertERC20(address _token, uint256 _amount) public returns (bool)
{
if(isWrapper(_token)) return unwrapERC20toERC223(_token, _amount);
else return wrapERC20toERC223(_token, _amount);
}
function isWrapper(address _token) public view returns (bool)
{
return erc20Origins[_token] != address(0) || erc223Origins[_token] != address(0);
}
function extractStuckERC20(address _token) external
{
require(msg.sender == address(0x01000B5fE61411C466b70631d7fF070187179Bbf));
safeTransfer(_token, address(0x01000B5fE61411C466b70631d7fF070187179Bbf), IERC20(_token).balanceOf(address(this)) - erc20Supply[_token]);
}
function safeTransfer(address token, address to, uint value) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
}
function safeTransferFrom(address token, address from, address to, uint value) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment