1

i've been trying to mix ERC20 functionality with ERC223 function overloading technique and create a bridge for backwards compatibility using SafeERC20.

For relevancy sake i renamed SafeERC20 into SafeERC223 but the problem is that since it's made only for ERC20 it only expects that we will always have 1 transfer function not 2 like in ERC223 case.

What we have so far:

  • Address Library
  • SafeERC223 Library
  • ERC223Interface Contract
  • ERC223Receiving Contract // ERC223 tokenFallback() emitter function
  • ERC223Token Contract
  • ERC223Contract

The new cooler way of doing SafeERC20 brings an interesting challenge for ERC223 function overloading techniques since it expect only 1 transfer function

Code

pragma solidity ^0.5.2;

/**
 * Utility library of inline functions on addresses
 */
library Address {
    /**
     * Returns whether the target address is a contract
     * @dev This function will return false if invoked during the constructor of a contract,
     * as the code is not actually created until after the constructor finishes.
     * @param account address of the account to check
     * @return whether the target address is a contract
     */
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        // XXX Currently there is no better way to check if there is a contract in an address
        // than to check the size of the code at that address.
        // See https://ethereum.stackexchange.com/a/14016/36603
        // for more details about how this works.
        // TODO Check this again before the Serenity release, because all addresses will be
        // contracts then.
        // solhint-disable-next-line no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }
}

/** 
 * @title SafeMath
 * @dev Unsigned math operations with safety checks that revert on error.
 */
library SafeMath {
    /**
     * @dev Multiplies two unsigned integers, reverts on overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b);

        return c;
    }

    /**
     * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Adds two unsigned integers, reverts on overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a);

        return c;
    }

    /**
     * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
     * reverts when dividing by zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0);
        return a % b;
    }
}

contract ERC223Interface {
    function totalSupply() external view returns (uint256);
    function balanceOf(address who) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function transfer(address to, uint256 value, bytes calldata data) external returns (bool);
    function approve(address spender, uint256 value) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
    function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
    function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value, bytes data);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
 * @title SafeERC223
 * @dev Wrappers around ERC223 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC223 for ERC223;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC223 {
    using SafeMath for uint256;
    using Address for address;

    function safeTransfer(ERC223Interface token, address to, uint256 value) internal {
        require(token.transfer(to, value)); // this method works but i'm not sure if it's safe to use
    }

    function safeTransfer(ERC223Interface token, address to, uint256 value) internal {
        callOptionalReturn(token, abi.encodeWithSelector(msg.sig, to, value)); // this method does not work when a contract executes safeTransfer
    }

    function safeTransferFrom(ERC223Interface token, address from, address to, uint256 value) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    function safeApprove(ERC223Interface token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require((value == 0) || (token.allowance(address(this), spender) == 0));
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(ERC223Interface token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).add(value);
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(ERC223Interface token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).sub(value);
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function callOptionalReturn(ERC223Interface token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves.

        // A Solidity high level call has three parts:
        //  1. The target address is checked to verify it contains contract code
        //  2. The call itself is made, and success asserted
        //  3. The return value is decoded, which in turn checks the size of the returned data.

        require(address(token).isContract());

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = address(token).call(data);
        require(success);

        if (returndata.length > 0) { // Return data is optional
            require(abi.decode(returndata, (bool)));
        }
    }
}

contract ReentrancyGuard {
    using SafeMath for uint256;
    uint256 private _guardCounter;

    constructor () internal {
        _guardCounter = 1;
    }

    modifier nonReentrant() {
        _guardCounter = _guardCounter.add(1);
        uint256 localCounter = _guardCounter;
        _;
        require(localCounter == _guardCounter);
    }
}

contract ERC223ReceivingContract { 
    /**
     * @dev Standard ERC223 function that will handle incoming token transfers.
     *
     * @param _from  Token sender address.
     * @param _value Amount of tokens.
     * @param _data  Transaction metadata.
     */
    function tokenFallback(address _from, uint256 _value, bytes memory _data) public;
}

contract ERC223Token is ERC223Interface {
    using SafeMath for uint256;

    address private _owner; 
    // i know it's not really private :)) but clogs remix compiler :)
    // https://medium.com/swlh/ethereum-aint-hiding-your-secrets-703e89088937

    string  public  constant name = "ERC223";
    string  public  constant symbol = "ERC223";
    uint8   public  constant decimals = 18;
    uint256 private constant _totalSupply = 10000000 * (uint256(10) ** decimals);

    mapping (address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) private _allowed;

    constructor() public {
        _owner = msg.sender;
        _balances[_owner] = _totalSupply;
        emit Transfer(address(0), _owner, _totalSupply);
    }

    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address owner) public view returns (uint256 balance) {
        return _balances[owner];
    }

    function transfer(address to, uint256 value) public returns (bool success) {
        require(to != address(0));
        require(value > 0 && balanceOf(msg.sender) >= value);
        require(balanceOf(to).add(value) > balanceOf(to));

        uint256 codeLength;
        bytes memory empty;

        assembly {
            codeLength := extcodesize(to)
        }

        _balances[msg.sender] = _balances[msg.sender].sub(value);
        _balances[to] = _balances[to].add(value);

        if(codeLength>0) {
            ERC223ReceivingContract receiver = ERC223ReceivingContract(to);
            receiver.tokenFallback(msg.sender, value, empty);
        }

        emit Transfer(msg.sender, to, value, empty);
        return true;
    }

    function transfer(address to, uint256 value, bytes memory data) public returns (bool success) {
        require(to != address(0));
        require(value > 0 && balanceOf(msg.sender) >= value);
        require(balanceOf(to).add(value) > balanceOf(to));

        uint256 codeLength;

        assembly {
            codeLength := extcodesize(to)
        }

        _balances[msg.sender] = _balances[msg.sender].sub(value);
        _balances[to] = _balances[to].add(value);

        if(codeLength>0) {
            ERC223ReceivingContract receiver = ERC223ReceivingContract(to);
            receiver.tokenFallback(msg.sender, value, data);
        }

        emit Transfer(msg.sender, to, value, data);
        return true;
    }

    function approve(address spender, uint256 value) public returns (bool success) {
        _allowed[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }

    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowed[owner][spender];
    }

    function transferFrom(address from, address to, uint256 value) public returns (bool success) {
        require(to != address(0));
        require(value <= _balances[from]);
        require(value <= _allowed[from][msg.sender]);

        _balances[from] = _balances[from].sub(value);
        _balances[to] = _balances[to].add(value);
        _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value);
        emit Transfer(from, to, value);
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public returns (bool success) {
        _allowed[msg.sender][spender] = _allowed[msg.sender][spender].add(addedValue);
        emit Approval(msg.sender, spender, _allowed[msg.sender][spender]);
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool success) {
        uint256 oldValue = _allowed[msg.sender][spender];
        if (subtractedValue > oldValue) {
            _allowed[msg.sender][spender] = 0;
        } else {
            _allowed[msg.sender][spender] = oldValue.sub(subtractedValue);
        }
        emit Approval(msg.sender, spender, _allowed[msg.sender][spender]);
        return true;
    }

    function unlockERC20Tokens(address tokenAddress, uint256 tokens) public returns (bool success) {
        require(msg.sender == _owner);
        return ERC223Interface(tokenAddress).transfer(_owner, tokens);
    }

    function () external payable {
        revert("This contract does not accept ETH");
    }

}

contract ERC223Contract is ReentrancyGuard {
    using SafeMath for uint256;

    ERC223Interface private token;

    constructor(ERC223Interface _token) public {
        token = _token;
        emit Created("Successfully created ERC223 Contract");
    }

    function getBlockNumber() public view returns (uint256) {
        return block.number;
    }

    function getData() public pure returns (bytes memory) {
        return msg.data;
    }

    function getSignature() public pure returns (bytes4) {
        return msg.sig;
    }

    function () external {
      //if ether is sent to this address, send it back.
      revert();
    }

    function tokenFallback(address player, uint tokens, bytes memory data) public nonReentrant {
        require(msg.sender == address(token));
        emit DepositedERC223Token(player, tokens, data);
    }

    event Created(string);
    event DepositedERC223Token(address from, uint value, bytes data);
}

contract ICO is ReentrancyGuard, Owned{
    using SafeMath for uint256;
    using SafeERC223 for ERC223Interface;

    // TOKEN INTERFACE,
    ERC223Interface private token;

    // ICO WALLET
    address payable private wallet;

    // TOTAL ETHER RAISED
    uint public totalRaised;

    // TOTAL TOKENS DISTRIBUTED
    uint public tokensSold;

    // KYC VALIDATION, ADDRESS DEPOSIT LIMIT
    mapping(address => bool) private _kyc;
    mapping(address => uint) public senderLimit;

    event TokensPurchased(address indexed purchaser, uint256 value, uint256 amount);

    constructor(ERC223Interface _token, address payable _wallet) public {
        require(address(_token) != address(0));
        require(address(_wallet) != address(0));
        token = _token;
        wallet = _wallet;
    }

    // GET OWNER ADDRESS
    function tokenOwnerAddress() public view returns(address) {
        return token.getOwner();
    }

    // GET ICO ADDRESS
    function icoAddress() public view returns(address) {
        return address(this);
    }

    // GET CONTRACT TOTAL TOKENS LEFT FOR SALE
    function checkBalance() public view returns(uint) {
        return token.balanceOf(address(this));
    }

    // KYC - VALIDATE OR INVALIDATE ADDRESS
    function updateKYC(address userAddress, bool status) public onlyOwner returns(bool) {
        _kyc[userAddress] = status;
        return status;
    }

    // KYC - CHECK IF ADDRESS HAS BEEN VALIDATED
    function checkKYC(address userAddress) public view returns(bool) {
        return _kyc[userAddress];
    }

    // PAYABLE FUNCTION
    function () external payable {
        // buyTokens();
        buyTokens(msg.sender);
    }

    //----------------------------------------------------
    // 1,000 Tokens == 1 ETH => 1 ETH + 50% BONUS = 1,500 LVC
    //----------------------------------------------------
    function buyTokens(address beneficiary) public nonReentrant payable {
        require(checkBalance() != 0);
        require(msg.sender == beneficiary);
        uint256 weiAmount = msg.value;
        uint tokens;

        // PRESALE - 50% BONUS
        tokens = weiAmount * 1500;

        tokensSold = tokensSold.add(tokens);
        totalRaised = totalRaised.add(weiAmount);

        _processPurchase(beneficiary, tokens);
        _forwardFunds();

        senderLimit[beneficiary] = senderLimit[beneficiary].add(weiAmount);

        if(_kyc[beneficiary] == false){
            require(weiAmount >= .1 ether && weiAmount <= 25 ether && senderLimit[beneficiary] <= 25 ether);
        } else if (_kyc[beneficiary] == true) {
            require(weiAmount >= .1 ether && weiAmount <= 100 ether && senderLimit[beneficiary] <= 100 ether);
        }

        emit TokensPurchased(beneficiary, weiAmount, tokens);
    }

    function _deliverTokens(address beneficiary, uint256 tokenAmount) internal {
        token.safeTransfer(beneficiary, tokenAmount);
    }

    function _processPurchase(address beneficiary, uint256 tokenAmount) internal {
        _deliverTokens(beneficiary, tokenAmount);
    }

    function _forwardFunds() internal {
        wallet.transfer(msg.value);
    }

    function _transferToBonusReserve(address beneficiary, uint256 tokenAmount) internal {
        _deliverTokens(beneficiary, tokenAmount);
    }

    function unlockBlockedERC20Tokens(address tokenAddress, uint tokens) public onlyOwner returns (bool success) {
        return ERC223Interface(tokenAddress).transfer(owner, tokens);
    }

    function tokenFallback(address owner, uint tokens, bytes memory data) public nonReentrant {
        require(msg.sender == address(token));
        require(owner == tokenOwnerAddress());
        emit DepositedTokens(owner, tokens, data);
    }

    event DepositedTokens(address from, uint value, bytes data);
}

This is the error i get if i don't comment out the second transfer function from the ERC223Interface.

TypeError: Member "transfer" not unique after argument-dependent lookup in 
contract ERC223Interface.callOptionalReturn(token, abi.encodeWithSelector( token.transfer.selector, to, value));

UPDATE

  • Check SafeERC223 - msg.sig throws while the old SafeERC223 works...
  • Added a Crowdsale example where you can test how it fails

Steps to reproduce:

Deploy ERC223 Token

  1. Deploy ERC223 Token
  2. Deploy ERC223 ICO with 2 arguments (token address, ETH wallet)
  3. Owner transfers let's say half of the totalSupply to the ICO Contract.
  4. Contributor sends 1 ETH and should receive 1500 Tokens. This step uses SafeERC223 and should transfer tokens to the contributor but if we use msg.sig it fails, if we only use the old way of doing things works but i don't know if it's safe to use.

Hint: SafeTransfer is used inside _processPurchase() If we switch comment the msg.sig method and decomment the old SafeTransfer it should work as it previously did but i have no clue how secure it is this way.

CHeers !

1

If I'm not mistaken, the issue is token.transfer.selector is trying to work out the signature from the source code.

It would be equivalent to abi.encodeWithSelector(bytes4(keccak256(bytes(signature)));

https://solidity.readthedocs.io/en/v0.5.2/units-and-global-variables.html?highlight=selector#abi-encoding-and-decoding-functions

Differentiation is not possible this way because you want "both".

You're interested in the function signature that arrived with the transaction. You should want those to map perfectly to implementation contracts. If my thinking is correct, it would be sufficient to pick it out with msg.sig and pass it through.

msg.sig: https://solidity.readthedocs.io/en/v0.5.2/units-and-global-variables.html?highlight=selector#block-and-transaction-properties

That would leave you with this:

function safeTransfer(ERC223Interface token, address to, uint256 value) internal {
    callOptionalReturn(token, abi.encodeWithSelector(msg.sig, to, value));
}

No warranty ;-)

Hope it helps.

  • Really amazing Rob, flawless thinking :) Going to test out which function selector gets called when we do transfers with msg.sig and come back with my findings and a Git Repo. Also I added the receiving ERC223Contract code, didn't see i omitted that part. Thank you ! – Liberalite Apr 3 at 11:22
  • Cool. I'm curious about taking a closer look. happy to help. – Rob Hitchens - B9lab Apr 3 at 14:21
  • Hey Rob, i spent all night making web3 tests and figured out that i should switch to ethers.js, i'm going to upload to git a clean version using ethers.js shortly. Also i tried to use the safeTransfer method in a ICO Contract and it fails with msg.sig. I have updated my question to reflect the errors. Check SafeERC223 Contract again and read the comments. Thank you ! – Liberalite Apr 4 at 13:03
  • I'm curious about the whole thing. When you have a repo ready and I have some time I plan to take a closer look. – Rob Hitchens - B9lab Apr 4 at 14:00
  • 1
    I'm planning to take a close look at this. I'd like to invite you to reach out directly through LinkedIn which you can find your way to via my profile, here. Thinking of a brief chat about potential applications and goals. – Rob Hitchens - B9lab Apr 16 at 21:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.