Implementing Support for ERC20 Tokens in Plasma Cash Using the Example of Custom Tokens

Our developer team set a goal of enabling the support of the ERC20 token in Plasma. But to do this, we had to establish an algorithm for receiving ERC20 tokens during the deposit. Everything is simple with ETH: the user can request the ‘deposit’ function with an ETH transaction.
 
As a rule, ERC20 token users exchange tokens using the `transfer` function of a smart contract here.
  1. ```    
  2. /**   
  3. * @dev transfer token for a specified address   
  4. * @param _to the address to transfer to.   
  5. * @param _value The amount to be transferred.   
  6. */      
  7. function transfer(address _to, uint256 _value) public returns (bool) {      
  8.    require(_to != address(0));      
  9.    require(_value <= balances[msg.sender]);      
  10.    // SafeMath.sub will throw if there is not enough balance.      
  11.    balances[msg.sender] = balances[msg.sender].sub(_value);      
  12.    balances[_to] = balances[_to].add(_value);      
  13.    Transfer(msg.sender, _to, _value);      
  14.    return true;      
  15. }      
  16. ```    
As you can see from the code, only the owner of ERC20 tokens can send them to another address.
 

Algorithm for Transferring ERC20 Tokens

 
We can take advantage of two great features provided in ERC20 and, in particular, in the Opporty smart contract,
 
`approve` here.
  1. ```      
  2. /**   
  3. * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.   
  4. *   
  5. * @param _spender The address which will spend the funds.   
  6. * @param _value The amount of tokens to be spent.   
  7. */      
  8. function approve(address _spender, uint256 _value) public returns (bool) {      
  9.    allowed[msg.sender][_spender] = _value;      
  10.    Approval(msg.sender, _spender, _value);      
  11.    return true;      
  12. }      
  13. ```    
The token owner requests this function and thus signals to the blockchain that they allow the transfer of a certain number of ERC20 tokens to a specific address.
 
`transferFrom` here.
  1. ```      
  2. /**   
  3. * @dev Transfer tokens from one address to another   
  4. * @param _from address The address which you want to send tokens from   
  5. * @param _to address The address which you want to transfer to   
  6. * @param _value uint256 the amount of tokens to be transferred   
  7. */      
  8. function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {      
  9.    require(_to != address(0));      
  10.    require(_value <= balances[_from]);      
  11.    require(_value <= allowed[_from][msg.sender]);      
  12.    balances[_from] = balances[_from].sub(_value);    
  13.    balances[_to] = balances[_to].add(_value);      
  14.    allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);      
  15.    Transfer(_from, _to, _value);      
  16.    return true;      
  17. }      
  18. ```    
This function is requested by the party that wants to receive tokens.
 
Therefore, we get the following algorithm,
  1. The user requests the `approve` function of the ERC20 smart contract, specifies the address of the Plasma Cash smart contract and the amount of ERC20 tokens they want to transfer to the deposit.
  2. The user requests the `deposit` function of the Plasma Cash smart contract (we will provide more information about that later), which in turn requests the `transferFrom` function of the ERC20 smart contract.
  3. The user withdraws the Plasma token in the standard way: using the `transfer` function of the ERC20 smart contract, the Plasma Cash smart contract transfers tokens to the receiver's address.
As evident from the above algorithm, we need to refine the Plasma Cash smart contract.
 

Refining the Plasma Cash Smart Contract

 
We add an ERC20 smart contract declaration,
  1. ```      
  2. contract ERC20 {      
  3.    function totalSupply() public constant returns (uint);      
  4.    function balanceOf(address tokenOwner) public constant returns (uint balance);      
  5.    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);      
  6.    function transfer(address to, uint tokens) public returns (bool success);      
  7.    function approve(address spender, uint tokens) public returns (bool success);      
  8.    function transferFrom(address from, address to, uint tokens) public returns (bool success);      
  9.    event Transfer(address indexed from, address indexed to, uint tokens);      
  10.    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);      
  11. }      
  12. ```    
Next, we establish a new token structure that will support both ETH coins and ERC20 tokens.
  1. ```      
  2. uint8 constant token_currency_eth = 1;      
  3. uint8 constant token_currency_erc20 = 2;      
  4. struct Token {      
  5.    uint value;      
  6.    uint8 currency;      
  7.    address addr;      
  8. }      
  9. ```    
Clarifications
  • `token_currency_eth` - a constant defining the token currency as ETH;
  • `token_currency_erc20` - a constant defining the token currency as ERC20;
  • `value` - token amount;
  • `currency` - 1 - ETH, 2-ERC20;
  • `address` - address of the smart contract of a ERC20 token.
The next step is to add the depositERC20 function,
  1. ```      
  2. function depositERC20(address _contract_addr, uint _value) public payable {     
  3. ERC20(_contract_addr).transferFrom(msg.sender, address(this), _value);      
  4. uint token_id = uint(keccak256(msg.sender, _value, deposit_blk));      
  5. Token memory newToken = Token({      
  6.    currency : token_currency_erc20,      
  7.    value : _value,      
  8.    addr : _contract_addr      
  9.    });      
  10.    tokens[token_id] = newToken;      
  11.    deposit_blk += 1;      
  12.    emit DepositAdded(msg.sender, msg.value, token_id, current_blk);      
  13. }      
  14. ```    
Clarifications
  • `_contract_addr` - the address of a ERC20 smart contract;
  • `_value` - the amount of ERC20 tokens.
ERC20 tokens are first transferred to the address of the Plasma Cash smart contract, and then the Plasma token is added to the blockchain.
 
Also, we refine `finalizeExits` to support the withdrawal of ERC20 tokens.
  1. ```      
  2. function finalizeExits() public returns (bool success) {      
  3.    // get all exits by priority      
  4.    while (exits.data.length != 0 && block.timestamp > exits.peek() + twoWeeks) {      
  5.       uint priority = exits.pop();      
  6.       for (uint i = 0; i < exit_ids[priority].length; i++) {      
  7.          uint index = exit_ids[priority][i];      
  8.          Exit memory record = exitRecords[index];      
  9.          // finalize exits      
  10.          if (tokens[index].currency == token_currency_eth)      
  11.          record.new_owner.transfer(tokens[index].value - record.total_fee);   
  12.          else if (tokens[index].currency == token_currency_erc20) {      
  13.          ERC20(tokens[index].addr).transfer(record.new_owner, tokens[index].value - record.total_fee);      
  14.       }      
  15.    emit ExitCompleteEvent(current_blk, record.block_num, record.token_id, tokens[record.token_id].value, record.total_fee);      
  16.       delete exitRecords[index];      
  17.       delete tokens[index];      
  18.       }      
  19.    delete exit_ids[priority];    
  20. }      
  21. return true;    
  22. }    
  23. ```    

How This Can Be Used in NodeJs

 
Let's make wrapping for the ERC20 smart contract here.
 
The key parts of this file,
  1. ```javascript      
  2. async estimateApproveGas(spender, tokens, address) {      
  3.    return await this.contract.methods      
  4.    .approve(spender, tokens)      
  5.    .estimateGas({from: address});      
  6. }    
  7. async approve(spender, tokens, address, gas) {      
  8.    return await this.contract.methods      
  9.    .approve(spender, tokens)      
  10.    .send({from: address, gas: parseInt(gas) + 15000});      
  11. }      
  12. ...      
  13. // erc20 address of the smart contract      
  14. const contractHandler = new ContractHandler({address:"0x8ef7c0cf8fe68076446803bb9035bd2a3a5e1581"});    
  15. ```   
Refining the wrapping for Plasma Cash smart contract here.
  1. ```javascript      
  2. async estimateDepositERC20(contract, value, address) {      
  3.    return await this.contract.methods      
  4.    .depositERC20(contract, value)      
  5.    .estimateGas({from: address});      
  6. }     
  7. async depositERC20(contract, value, address, gas) {      
  8.    return await this.contract.methods      
  9.    .depositERC20(contract, value)      
  10.    .send({from: address, gas: parseInt(gas) + 15000});      
  11. }      
  12. ```    
Next, we make a deposit.
  1. ```javascript      
  2. // Unlock the wallet from which we want to transfer ERC20 tokens.      
  3. await web3.eth.personal.unlockAccount(address, password);      
  4. // Determine how much gas is needed to allow the transfer of tokens.      
  5. const gasApprove = await erc20Contract.estimateApproveGas(plasmaContract.address, value, address);    
  6. // Allow the transfer of tokens.      
  7. const approveAnswer = await erc20Contract.approve(plasmaContract.address, value, address, gasApprove);      
  8. // Calculate the cost of a deposit.      
  9. const gas = await plasmaContract.estimateDepositERC20(erc20Contract.address, value, address);      
  10. // Perform the deposit function.      
  11. const answer = await plasmaContract.depositERC20(erc20Contract.address, value, address, gas);      
  12. // Get a Plasma tokenId.      
  13. const tokenId = answer.events.DepositAdded.returnValues.tokenId;      
  14. console.log(tokenId);      
  15. ```    
As a result, we managed to implement support for ERC20 tokens in Plasma Cash, while not making changes to the ERC20 smart contract.
 
For more details, check out GitHub,
The article was written in co-authorship with Oleksandr Nashyvan, a Senior Developer at Clever Solution Inc.