Glossary
RootChain | The base chain of the polygon, deploying a series of contracts such as polygon staking, refers to EthereumMainnet or Goerli |
ChildChain | Polygon mainnet or Mumbai testnet |
RootToken | The token corresponding to the RootChain side of the bridge, that is, the token deployed on the Ethereum chain |
ChildToken | The token corresponding to the ChildChain side of the bridge, that is, the token deployed on the Polygon chain |
PoS-Bridge & Plasma Bridge
The bridge is used to help transfer assets between RootChain and ChildChain, and is realized by a series of contracts.
Polygon provides two types of bridges, Plasma Bridge and PoS Bridge.
Due to Plasma's exit mechanism, Plasma Bridge is more secure, and there is a seven-day withdrawal period for withdrawal from Polygon to Ethereum. Correspondingly,
PoS Bridge only needs one checkpoint period.
PoS Bridge Implementation Principle
PoS Bridge is implemented by a series of contracts, the contract link https://github.com/maticnetwork/contracts/blob/main/contracts .
1. Create token mapping
If your token needs PoS Bridge to support the transmission between Ethereum and Polygon, you need to add a mapping, that is, the corresponding relationship between RootToken and ChildToken.
Polygon provides an operation page https://mapper.polygon.technology/ to help you submit mapping requests. After submitting the request, the Polygon Team will help you add the mapping relationship to the contract.
The mapped contract code is at https://github.com/maticnetwork/contracts/blob/main/contracts/common/Registry.sol
/**
* @dev Map root token to child token
* @param _rootToken Token address on the root chain
* @param _childToken Token address on the child chain
* @param _isERC721 Is the token being mapped ERC721
*/
function mapToken(
address _rootToken,
address _childToken,
bool _isERC721
) external onlyGovernance {
require(_rootToken != address(0x0) && _childToken != address(0x0), "INVALID_TOKEN_ADDRESS");
rootToChildToken[_rootToken] = _childToken;
childToRootToken[_childToken] = _rootToken;
isERC721[_rootToken] = _isERC721;
IWithdrawManager(contractMap[WITHDRAW_MANAGER]).createExitQueue(_rootToken);
emit TokenMapped(_rootToken, _childToken);
}
Only the Polygon team has permission to call this interface.
2. Token transfer from Ethereum to Polygon
When the token needs to be transferred from Ethereum to Polygon, it is handled by the Depositmanager contract deployed on Ethereum.
https://github.com/maticnetwork/contracts/blob/main/contracts/root/depositManager/DepositManager.sol
rootchain
Taking ERC20 as an example, when the user transfers the token from Ethereum to Polygon, it is locked in the contract of Ethereum, and this message is sent to the ChildERC20 contract of Polygon through the StateSender mechanism. The StateSender mechanism is described in other documents Description is a two-way communication mechanism between Ethereum and Polygon implemented by Polygon.
function depositERC20(address _token, uint256 _amount) external {
depositERC20ForUser(_token, msg.sender, _amount);
}
function depositERC20ForUser(
address _token,
address _user,
uint256 _amount
) public {
require(_amount <= maxErc20Deposit, "exceed maximum deposit amount");
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
_safeCreateDepositBlock(_user, _token, _amount);
}
function _safeCreateDepositBlock(
address _user,
address _token,
uint256 _amountOrToken
) internal onlyWhenUnlocked isTokenMapped(_token) {
_createDepositBlock(
_user,
_token,
_amountOrToken,
rootChain.updateDepositId(1) /* returns _depositId */
);
}
function _createDepositBlock(
address _user,
address _token,
uint256 _amountOrToken,
uint256 _depositId
) internal {
deposits[_depositId] = DepositBlock(keccak256(abi.encodePacked(_user, _token, _amountOrToken)), now);
stateSender.syncState(childChain, abi.encode(_user, _token, _amountOrToken, _depositId));
emit NewDepositBlock(_user, _token, _amountOrToken, _depositId);
}
child chain
ChildChain contract code address on the Polygon chain
https://github.com/maticnetwork/contracts/blob/main/contracts/child/ChildChain.sol
After the root chain sends the deposit message through the stateSender, after a checkpoint cycle, the message will be received in the childchain contract. And call the deposit interface of the corresponding childToken contract.
function onStateReceive(
uint256, /* id */
bytes calldata data
) external onlyStateSyncer {
(address user, address rootToken, uint256 amountOrTokenId, uint256 depositId) = abi
.decode(data, (address, address, uint256, uint256));
depositTokens(rootToken, user, amountOrTokenId, depositId);
}
function depositTokens(
address rootToken,
address user,
uint256 amountOrTokenId,
uint256 depositId
) internal {
// check if deposit happens only once
require(deposits[depositId] == false);
// set deposit flag
deposits[depositId] = true;
// retrieve child tokens
address childToken = tokens[rootToken];
// check if child token is mapped
require(childToken != address(0x0));
ChildToken obj;
if (isERC721[rootToken]) {
obj = ChildERC721(childToken);
} else {
obj = ChildERC20(childToken);
}
// deposit tokens
obj.deposit(user, amountOrTokenId);
// Emit TokenDeposited event
emit TokenDeposited(
rootToken,
childToken,
user,
amountOrTokenId,
depositId
);
}
ChildChain is deployed by the Polygon team, not every token developer. Token developers must implement the deposit and withdraw methods in the contract.
When the deposit method is called in the childchain, the token contract will issue a corresponding number of tokens for the cross-chain user mint.
Examples are as follows:
pragma solidity 0.6.6;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
contract ChildERC20 is ERC20,
{
using SafeMath for uint256;
constructor(string memory name, string memory symbol, uint8 decimals) public ERC20(name, symbol) {
_setupDecimals(decimals);
// can't mint here, because minting in child chain smart contract's constructor not allowed
// _mint(msg.sender, 10 ** 27);
}
function deposit(address user, bytes calldata depositData) external {
uint256 amount = abi.decode(depositData, (uint256));
// `amount` token getting minted here & equal amount got locked in RootChainManager
_totalSupply = _totalSupply.add(amount);
_balances[user] = _balances[user].add(amount);
emit Transfer(address(0), user, amount);
}
function withdraw(uint256 amount) external {
_balances[msg.sender] = _balances[msg.sender].sub(amount, "ERC20: burn amount exceeds balance");
_totalSupply = _totalSupply.sub(amount);
emit Transfer(msg.sender, address(0), amount);
}
}