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.
Important Addresses and Links
Polygon RPC endpoint: (Sign up for a free RPC link at https://rpc.maticvigil.com/)
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.
- The Deposit Manager Contract is approved to spend X on behalf of
msg.sender
- Contract:
ERC20.sol
andDepositManager.sol
- Network: Goerli
- Function:
approve
- Contract:
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)
);
The Deposit Manager transfers the amount from
msg.sender
to itself- Contract:
DepositManager.sol
- Network: Goerli
- Function:
despositERC20ForUser()
- Contract:
Transfer the amount of tokens from msg.sender to DepositManager. Emit NewDepositBlock event.
depositManagerContract.methods.depositERC20ForUser(
token,
user,
this.encode(amount)
);
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
- Invokes the standard
transfer
function of ERC20 contract.- Contract:
ChildERC20.sol
- Network: Polygon
- Function:
transfer
- Contract:
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
- 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);
- Contract:
4. Withdraw ERC20 tokens from Polygon to Goerli
Description: Withdraw assets (ERC20) from Polygon testnet to Goerli
Procedure of Withdrawal:
- Burn tokens on Polygon sidechain
- Submit proof of burn (the receipt of burn tx) on Root Chain
- This step is executed only after the block consisting of the burn tx has been included in a checkpoint on the Root Chain.
- After checkpoint submission, a successful execution of this step
- 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)
- Mints an
ExitNFT
token to the exitor's account - which is representative of the exit initiated on the child chain by the exitor
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.
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);
- Contract:
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:
- Get last child block from RootChain.sol - this is the number of blocks which was checkpointed last on the root chain.
- If the last checkpointed block is less than the block containing the burn transaction - wait until the block is checkpointed.
- If the checkpoint has been included
- find
HeaderBlockNumber
from RootChain.sol- 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.
- Query RootChain.sol with the header block number
- Build Block Proof, sample: https://github.com/maticnetwork/contracts/blob/fa6862dc6ddae97351aa1b4d16c087861b5a489e/contracts-core/helpers/proofs.js#L24
- Build Receipt Proof, sample: https://github.com/maticnetwork/contracts/blob/fa6862dc6ddae97351aa1b4d16c087861b5a489e/contracts-core/helpers/proofs.js#L106
- find
- 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,
]),
)
}
```- Contract:
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);