ERC777 Contract with ERC20 Tokens received

Hello guys.

Firstly I will explain what I would like to achieve. I need a contract which is allowing to receive only one specific ERC20 token (all other transactions should be rejected by the contract itself) and then that contract sends ETH token back to the sender.

Example: An address sends 10000 ERC20 X tokens to contract and contract sends back to sender 1 ETH.

I used an example code from this post

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC777/IERC777.sol";
import "@openzeppelin/contracts/introspection/IERC1820Registry.sol";
import "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";

/**
 * @title XToken777Recipient
 * @dev Very simple ERC777 Recipient
 */
contract XToken777Recipient is IERC777Recipient {

    IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
    bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient");

    IERC777 private _token;

    event DoneStuff(address operator, address from, address to, uint256 amount, bytes userData, bytes operatorData);

    constructor (address token) {
        _token = IERC777(token); // Also tried with ERC20 and it didn't work

        _erc1820.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this));
    }

    function tokensReceived(
        address operator,
        address from,
        address to,
        uint256 amount,
        bytes calldata userData,
        bytes calldata operatorData
    ) external override {
        require(msg.sender == address(_token), "XToken777Recipient: Invalid token");

        // do stuff
        emit DoneStuff(operator, from, to, amount, userData, operatorData);
    }
}

Is the “address token” parameter in the constructor the address of my “X Token” that i am sending to the contract?
No matter which token I send, the tokensReceived function is never triggered. For start I just want to achieve that tokensReceived function is triggered and then I will add other functions to send ETH back to sender.

I would like to know how the tokenReceived hook is triggered. Is it automatically triggered when transactions happens or do I need to call it somehow?

I am testing it on rinkeby testnet network. Maybe this hook doesn’t work properly on rinkeby testnet? Any suggestions for a specific network I should use?

Thank you in advance.

Regards

Hi, welcome! :wave:

I think you can have a look at this tutorial

Hi,

thank you for your reply. I linked the same post as you did which i have used it as a tutorial.

So if I add revert function inside of tokensReceived function all transactions should be reverted? I tried this and all transactions are confirmed. I don’t know what exactly am i doing wrong that tokensReceived is not triggered.

function tokensReceived(
        address operator,
        address from,
        address to,
        uint256 amount,
        bytes calldata userData,
        bytes calldata operatorData
    ) external override {
        revert();
        // require(msg.sender == address(_token), "XToken777Recipient: Invalid token");

        // do stuff
        // emit DoneStuff(operator, from, to, amount, userData, operatorData);
    }

So even though you have followed the tutorial, the tokensReceived function was still not triggered, right? If possible, you can share your script at here.

I will share the code I have it so far.

1. XToken777Recipient Contract

You can see that the code is the same as in this Simple ERC777 token example
I just added revert function into tokenReceived function

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC777/IERC777.sol";
import "@openzeppelin/contracts/introspection/IERC1820Registry.sol";
import "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";

contract XToken777Recipient is IERC777Recipient {
    IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
    bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient");

    IERC777 private _token;

    event DoneStuff(address operator, address from, address to, uint256 amount, bytes userData, bytes operatorData);

    constructor (address token) {
        _token = IERC777(token);

        _erc1820.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this));
    }

    function tokensReceived(
        address operator,
        address from,
        address to,
        uint256 amount,
        bytes calldata userData,
        bytes calldata operatorData
    ) external override {
        revert();
        require(msg.sender == address(_token), "XToken777Recipient: Invalid token");

        // do stuff
        emit DoneStuff(operator, from, to, amount, userData, operatorData);
    }
}
2. Truffle migration: 2_deploy.js
const XToken777Recipient = artifacts.require('XToken777Recipient');

require('@openzeppelin/test-helpers/configure')({ provider: web3.currentProvider, environment: 'truffle' });

const { singletons } = require('@openzeppelin/test-helpers');

module.exports = async (deployer, network, accounts) => {
    if (network === 'development') {
        // In a test environment an ERC777 token requires deploying an ERC1820 registry
        await singletons.ERC1820Registry(accounts[0]);
    }

    await deployer.deploy(XToken777Recipient, "0x8D8ab567C57De121C515c85c24f9B3d796c0872A"); // Contract address of XToken Testnet link: https://rinkeby.etherscan.io/token/0x8D8ab567C57De121C515c85c24f9B3d796c0872A
};
3. Truffle config: truffle-config.js
const HDWalletProvider = require('@truffle/hdwallet-provider');

module.exports = {
  networks: {
    development: {
      protocol: 'http',
      host: 'localhost',
      port: 8545,
      gas: 5000000,
      gasPrice: 5e9,
      networkId: '*',
    },
    rinkeby: {
      network_id: '4',
      provider: () => new HDWalletProvider(
          ['<privateKey>'],
          'https://rinkeby.infura.io/v3/<apiKey>'>,
          0,
          1,
      ),
      gasPrice: 10000000000, // 10 gwei
      gas: 6900000,
      from: '<ownerAddress>',
      timeoutBlocks: 500,
    }
  },
  mocha: {
    // timeout: 100000
  },
  compilers: {
    solc: {
      version: '0.8.0',
      docker: false,
      parser: 'solcjs',
      settings: {
        optimizer: {
          enabled: true,
          runs: 50000
        },
        evmVersion: 'istanbul',
      }
    }
  },
  db: {
    enabled: false
  }
};

Now i will explain the whole process step by step:

  1. Created an ERC20 token named: XToken Testnet (XTT)
  2. Created new user address and sent 5000 XTT to it
  3. Ran command: migrate --network rinkeby
2_deploy.js
===========

   Deploying 'XToken777Recipient'
   ------------------------------
   > transaction hash:    0x9c4feebdff10a771a31b5bf32d7db82de2e295c3e7f153e288c5b08044c63e09
   > Blocks: 0            Seconds: 8
   > contract address:    0x96aBa35C73Ff3d86f4d904B26C94CF373B214Cc6
   > block number:        8779769
   > block timestamp:     1623932892
   > account:             0x58C7716959efb3E6495793956F639Efc2a3412Aa
   > balance:             1.99554411
   > gas used:            220137 (0x35be9)
   > gas price:           10 gwei
   > value sent:          0 ETH
   > total cost:          0.00220137 ETH
  1. From XToken User I sent 10 XTT tokens to XToken777Recipient contract

So you can see that the transaction has been confirmed and should be reverted. You can see revert function in XToken777Recipient tokenReceived function.

Hi, were you able to resolve it?
Actually I'm trying to create a contract that will accept the UNI tokens. And after accepting get the sender address and how much tokens were sent. Then store it in a mapping and then emit an event. I have setup metamask with an account which have few tokens and I will use it to transfer UNI token to my contract in remix. I don't want to create any custom tokens.
So I'm just using the implementation of IERC777Recipient piece of code.

But within this code what must be passed to the constructor from remix when I deploy the contract?
_token = IERC777( token );