Skip to main content

Calling Plasma Contracts

For most developers, it is advisable and recommended to use the Matic.js library to interact with Polygon.

However, this page helps developers, who have a good understanding of smart contracts in Ethereum, to bypass the Matic.js library and interact directly with the Polygon smart contracts. This might help developers to understand the inner workings of Polygon, as well as to customise their interaction with Polygon to some extent.

Polygon RPC endpoint: (Sign up for a free RPC link at https://rpc.maticvigil.com/)

ContractABIGoerliPolygon
TEST (ERC20) token0x3f152B63Ec5CA5831061B2DccFb29a874C3175020x2d7882beDcbfDDce29Ba99965dd3cdF7fcB10A1e
Registry Root Contract0xeE11713Fe713b2BfF2942452517483654078154D
DepositManager (Proxy) Contract0x7850ec290A2e2F40B82Ed962eaf30591bb5f5C96
Child Chain Contract0x1EDd419627Ef40736ec4f8ceffdE671a30803c5e
WithdrawManager (Proxy) Contract0x2923C8dD6Cdf6b2507ef91de74F1d5E0F11Eac53

Tokens for testing

To get some test tokens on Goerli network, you can access the Polygon Faucet.

Polygon Explorer

You can also check transaction procesed on the Polygon Sidechain using the Polygon Explorer.

Workflow

1. Deposit ERC20 token from Goerli to Polygon

Description: To deposit assets (ERC20) from Goerli to Polygon.

Let the required amount of tokens be X.

  1. The Deposit Manager Contract is approved to spend X on behalf of msg.sender
    • Contract: ERC20.sol and DepositManager.sol
    • Network: Goerli
    • Function: approve

Call the standard approve function in ERC20 contract, approving DepositManager to transfer the amount of tokens.

RootERC20Contract.methods.approve(
this.depositManagerContract.options.address,
this.encode(amount)
);
  1. The Deposit Manager transfers the amount from msg.sender to itself

Transfer the amount of tokens from msg.sender to DepositManager. Emit NewDepositBlock event.

depositManagerContract.methods.depositERC20ForUser(
token,
user,
this.encode(amount)
);
note

Depositing tokens on Polygon mints new tokens for ChildERC20 contracts on Polygon. ChildERC20 contract is the contract that is deployed with the process of mapping, which is representative of the token present on main network.

2. Transfer tokens on Polygon

Description: Transfer tokens on Polygon testnet

  1. Invokes the standard transfer function of ERC20 contract.
    • Contract: ChildERC20.sol
    • Network: Polygon
    • Function: transfer
ChildERC20.methods
.transfer(
recipientAddress,
this.encode(amount)
)

3. Display account balances for users on Polygon

Description: Query ERC20 token balances for user on Polygon and Goerli

  1. Invokes the standard balanceOf function of ERC20 contract.
    • Contract: ChildERC20.sol
    • Network: Polygon
    • Function: balanceOf
      // ERC20TokenContract can be either on Goerli or Polygon
      ERC20TokenContract.methods.balanceOf(owner);

4. Withdraw ERC20 tokens from Polygon to Goerli

Description: Withdraw assets (ERC20) from Polygon testnet to Goerli

Procedure of Withdrawal:

  1. Burn tokens on Polygon sidechain
  2. Submit proof of burn (the receipt of burn tx) on Root Chain
    1. This step is executed only after the block consisting of the burn tx has been included in a checkpoint on the Root Chain.
    2. After checkpoint submission, a successful execution of this step
      1. marks the initiation of the Challenge Exit Period (which is a 7-day period on main network, and set to 5 minute on test networks)
      2. Mints an ExitNFT token to the exitor's account - which is representative of the exit initiated on the child chain by the exitor
    3. processExits burns the Exit NFT and transfers the tokens back from Deposit manager to the exitor.

Let X be the amount of tokens to be withdrawn.

  1. Submit withdraw request of X tokens on Polygon - burns the tokens and returns a tx ID.

    • Contract: ChildERC20.sol
    • Network: Polygon
    • Function: withdraw

    Burns tokens on Polygon and emits Withdraw event.

    ChildToken.methods.withdraw(amount);
  2. Use tx ID from previous step to create a withdraw transaction on Goerli

    • Contract: ERC20Predicate.sol
    • Network: Goerli
    • Function: startExitWithBurntTokens

    The function accepts payload data, which is the proof of burn of tokens performed in the previous transaction. This payload is generated off-chain.

    payload = await _buildPayloadForExit(burnTxHash);
    erc20PredicateContract.methods.startExitWithBurntTokens(payload);

    To build payload for the exit:

    1. Get last child block from RootChain.sol - this is the number of blocks which was checkpointed last on the root chain.
    2. If the last checkpointed block is less than the block containing the burn transaction - wait until the block is checkpointed.
    3. If the checkpoint has been included
      1. find HeaderBlockNumber from RootChain.sol
        1. From the last checkpointed block to the block consisting of burn transaction perform search (Matic.js performs a binary search) to find the block with the burn tx.
        2. Query RootChain.sol with the header block number
      2. Build Block Proof, sample: https://github.com/maticnetwork/contracts/blob/fa6862dc6ddae97351aa1b4d16c087861b5a489e/contracts-core/helpers/proofs.js#L24
      3. Build Receipt Proof, sample: https://github.com/maticnetwork/contracts/blob/fa6862dc6ddae97351aa1b4d16c087861b5a489e/contracts-core/helpers/proofs.js#L106
    4. Return hex-encoded string of bytes: headernumber, blockProof, block number of burn transaction, timestamp of the burn tx block, root of block, root of receipts, RLP encoded receipt bytes, receipt parent nodes, receipt path, logIndex
    private async _buildPayloadForExit(burnTxHash) {
    // check checkpoint
    const lastChildBlock = await rootChain.getLastChildBlock()
    const burnTx = await web3.eth.getTransaction(burnTxHash)
    const receipt = await web3.eth.getTransactionReceipt(burnTxHash)
    const block = await web3.eth.getBlock(
    burnTx.blockNumber,
    true /* returnTransactionObjects */
    )

    // check here if the burn tx has been checkpointed or not
    logger.info( { 'burnTx.blockNumber': burnTx.blockNumber, lastCheckPointedBlockNumber: lastChildBlock } )
    assert.ok(
    new BN(lastChildBlock).gte(new BN(burnTx.blockNumber)),
    'Burn transaction has not been checkpointed as yet',
    )
    // if the block has been checkpointed, move ahead

    const headerBlockNumber = await rootChainContract.findHeaderBlockNumber(burnTx.blockNumber)
    const headerBlock = await web3.call(
    rootChainContract
    .getRawContract()
    .methods.headerBlocks(this.encode(headerBlockNumber)),
    )
    logger.info({ 'headerBlockNumber': headerBlockNumber.toString(), headerBlock })

    // build block proof
    const blockProof = await Proofs.buildBlockProof(
    this.web3Client.getMaticWeb3(),
    headerBlock.start,
    headerBlock.end,
    burnTx.blockNumber,
    )
    // build receipt proof
    const receiptProof = await Proofs.getReceiptProof(
    receipt,
    block,
    this.web3Client.getMaticWeb3(),
    )
    return this._encodePayload(
    headerBlockNumber,
    blockProof,
    burnTx.blockNumber,
    block.timestamp,
    Buffer.from(block.transactionsRoot.slice(2), 'hex'),
    Buffer.from(block.receiptsRoot.slice(2), 'hex'),
    Proofs.getReceiptBytes(receipt), // rlp encoded
    receiptProof.parentNodes,
    receiptProof.path,
    // @todo logIndex can vary
    1, // logIndex
    )
    }
    ```

    To encode payload (returned in the previous function)

    ```javascript
    function _encodePayload(
    headerNumber,
    buildBlockProof,
    blockNumber,
    timestamp,
    transactionsRoot,
    receiptsRoot,
    receipt,
    receiptParentNodes,
    path,
    logIndex,
    ) {
    return ethUtils.bufferToHex(
    ethUtils.rlp.encode([
    headerNumber,
    buildBlockProof,
    blockNumber,
    timestamp,
    ethUtils.bufferToHex(transactionsRoot),
    ethUtils.bufferToHex(receiptsRoot),
    ethUtils.bufferToHex(receipt),
    ethUtils.bufferToHex(ethUtils.rlp.encode(receiptParentNodes)),
    ethUtils.bufferToHex(ethUtils.rlp.encode(path)),
    logIndex,
    ]),
    )
    }

    ```

  3. Process Exits

    Third and final step for the withdrawal process is to process pending exits on the root chain.

    • Contract: WithdrawManager.sol

    • Network: Goerli

    • Function: processExits

      withdrawManager.methods.processExits(token);