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.
- ```
-
-
-
-
-
- function transfer(address _to, uint256 _value) public returns (bool) {
- require(_to != address(0));
- require(_value <= balances[msg.sender]);
-
- balances[msg.sender] = balances[msg.sender].sub(_value);
- balances[_to] = balances[_to].add(_value);
- Transfer(msg.sender, _to, _value);
- return true;
- }
- ```
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,
- ```
-
-
-
-
-
-
- function approve(address _spender, uint256 _value) public returns (bool) {
- allowed[msg.sender][_spender] = _value;
- Approval(msg.sender, _spender, _value);
- return true;
- }
- ```
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.
- ```
-
-
-
-
-
-
- function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
- 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);
- Transfer(_from, _to, _value);
- return true;
- }
- ```
This function is requested by the party that wants to receive tokens.
Therefore, we get the following algorithm,
- 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.
- 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.
- 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,
- ```
- contract ERC20 {
- function totalSupply() public constant returns (uint);
- function balanceOf(address tokenOwner) public constant returns (uint balance);
- function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
- function transfer(address to, uint tokens) public returns (bool success);
- function approve(address spender, uint tokens) public returns (bool success);
- function transferFrom(address from, address to, uint tokens) public returns (bool success);
- event Transfer(address indexed from, address indexed to, uint tokens);
- event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
- }
- ```
Next, we establish a new token structure that will support both ETH coins and ERC20 tokens.
- ```
- uint8 constant token_currency_eth = 1;
- uint8 constant token_currency_erc20 = 2;
- struct Token {
- uint value;
- uint8 currency;
- address addr;
- }
- ```
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,
- ```
- function depositERC20(address _contract_addr, uint _value) public payable {
- ERC20(_contract_addr).transferFrom(msg.sender, address(this), _value);
- uint token_id = uint(keccak256(msg.sender, _value, deposit_blk));
- Token memory newToken = Token({
- currency : token_currency_erc20,
- value : _value,
- addr : _contract_addr
- });
- tokens[token_id] = newToken;
- deposit_blk += 1;
- emit DepositAdded(msg.sender, msg.value, token_id, current_blk);
- }
- ```
Clarifications
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.
- ```
- function finalizeExits() public returns (bool success) {
-
- while (exits.data.length != 0 && block.timestamp > exits.peek() + twoWeeks) {
- uint priority = exits.pop();
- for (uint i = 0; i < exit_ids[priority].length; i++) {
- uint index = exit_ids[priority][i];
- Exit memory record = exitRecords[index];
-
- if (tokens[index].currency == token_currency_eth)
- record.new_owner.transfer(tokens[index].value - record.total_fee);
- else if (tokens[index].currency == token_currency_erc20) {
- ERC20(tokens[index].addr).transfer(record.new_owner, tokens[index].value - record.total_fee);
- }
- emit ExitCompleteEvent(current_blk, record.block_num, record.token_id, tokens[record.token_id].value, record.total_fee);
- delete exitRecords[index];
- delete tokens[index];
- }
- delete exit_ids[priority];
- }
- return true;
- }
- ```
How This Can Be Used in NodeJs
Let's make wrapping for the ERC20 smart contract
here.
The key parts of this file,
- ```javascript
- async estimateApproveGas(spender, tokens, address) {
- return await this.contract.methods
- .approve(spender, tokens)
- .estimateGas({from: address});
- }
- async approve(spender, tokens, address, gas) {
- return await this.contract.methods
- .approve(spender, tokens)
- .send({from: address, gas: parseInt(gas) + 15000});
- }
- ...
-
- const contractHandler = new ContractHandler({address:"0x8ef7c0cf8fe68076446803bb9035bd2a3a5e1581"});
- ```
Refining the wrapping for Plasma Cash smart contract
here.
- ```javascript
- async estimateDepositERC20(contract, value, address) {
- return await this.contract.methods
- .depositERC20(contract, value)
- .estimateGas({from: address});
- }
- async depositERC20(contract, value, address, gas) {
- return await this.contract.methods
- .depositERC20(contract, value)
- .send({from: address, gas: parseInt(gas) + 15000});
- }
- ```
Next, we make a deposit.
- ```javascript
-
- await web3.eth.personal.unlockAccount(address, password);
-
- const gasApprove = await erc20Contract.estimateApproveGas(plasmaContract.address, value, address);
-
- const approveAnswer = await erc20Contract.approve(plasmaContract.address, value, address, gasApprove);
-
- const gas = await plasmaContract.estimateDepositERC20(erc20Contract.address, value, address);
-
- const answer = await plasmaContract.depositERC20(erc20Contract.address, value, address, gas);
-
- const tokenId = answer.events.DepositAdded.returnValues.tokenId;
- console.log(tokenId);
- ```
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.