https://github.com/ChainSafe/...背景 跨链桥的模式一般分为资金池,或者影子资产模式。然而,不管是哪一种模式都好,其实无一例外都是lock/release,或者 mint/burn两个方式。我觉得对于合约端来说,实现跨链的合约应该是有很成熟的方案,而且可能会存在一个模版式的解决方案,chainbridge的solidity我觉得是其中一种。下面很简单整理一下最关键的部分:
资金和逻辑分离 核心逻辑在Bridge合约,上面有权限控制,金库暂停的逻辑。这里核心的安全逻辑处理。资金则存储在handler模块,这个是单独部署的,有ERC20Handler,ERC721Hander。handler是通过callback参数传入Bridge合约的,最核心的有2个函数:Deposite和executeProposal。分别用来做资金存入和取出的。
function deposit(
bytes32 resourceID,
address depositer,
bytescalldata data
) external override onlyBridge returns (bytes memory) {
uint256amount;
(amount) = abi.decode(data, (uint));
address tokenAddress = _resourceIDToTokenContractAddress[resourceID];
require(_contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted");
if (_burnList[tokenAddress]) {
burnERC20(tokenAddress, depositer, amount);
} else {
lockERC20(tokenAddress, depositer, address(this), amount);
}
}function executeProposal(bytes32 resourceID, bytes calldata data) external override onlyBridge {
uint256amount;
uint256lenDestinationRecipientAddress;
bytesmemory destinationRecipientAddress;
(amount, lenDestinationRecipientAddress) = abi.decode(data, (uint, uint));
destinationRecipientAddress = bytes(data[64:64 + lenDestinationRecipientAddress]);
bytes20 recipientAddress;
address tokenAddress = _resourceIDToTokenContractAddress[resourceID];
assembly {
recipientAddress := mload(add(destinationRecipientAddress, 0x20))
}require(_contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted");
if (_burnList[tokenAddress]) {
mintERC20(tokenAddress, address(recipientAddress), amount);
} else {
releaseERC20(tokenAddress, address(recipientAddress), amount);
}
}
Proposal 【chainbridge solidity核心简要分析】Proposal字面就是提案的意思。因为资金取出是一个很危险的操作,所以这个操作在chainBridge这里需要一个委员会进行投票,只有在投票通过后,提案才能被确认。资金才可以被取出。如果把Proposal取消的话,可以变成中心化操作,但要有更强的权限控制管理。
function voteProposal(uint8 domainID, uint64 depositNonce, bytes32 resourceID, bytes calldata data) external onlyRelayers whenNotPaused {
address handler = _resourceIDToHandlerAddress[resourceID];
uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(domainID);
bytes32 dataHash = keccak256(abi.encodePacked(handler, data));
Proposal memory proposal = _proposals[nonceAndID][dataHash];
require(_resourceIDToHandlerAddress[resourceID] != address(0), "no handler for resourceID");
if (proposal._status == ProposalStatus.Passed) {
executeProposal(domainID, depositNonce, data, resourceID, true);
return;
}address sender = _msgSender();
require(uint(proposal._status) <= 1, "proposal already executed/cancelled");
require(!_hasVoted(proposal, sender), "relayer already voted");
if (proposal._status == ProposalStatus.Inactive) {
proposal = Proposal({
_status : ProposalStatus.Active,
_yesVotes : 0,
_yesVotesTotal : 0,
_proposedBlock : uint40(block.number) // Overflow is desired.
});
emit ProposalEvent(domainID, depositNonce, ProposalStatus.Active, dataHash);
} else if (uint40(sub(block.number, proposal._proposedBlock)) > _expiry) {
// if the number of blocks that has passed since this proposal was
// submitted exceeds the expiry threshold set, cancel the proposal
proposal._status = ProposalStatus.Cancelled;
emit ProposalEvent(domainID, depositNonce, ProposalStatus.Cancelled, dataHash);
}if (proposal._status != ProposalStatus.Cancelled) {
proposal._yesVotes = (proposal._yesVotes | _relayerBit(sender)).toUint200();
proposal._yesVotesTotal++;
// TODO: check if bit counting is cheaper.emit ProposalVote(domainID, depositNonce, proposal._status, dataHash);
// Finalize if _relayerThreshold has been reached
if (proposal._yesVotesTotal >= _relayerThreshold) {
proposal._status = ProposalStatus.Passed;
emit ProposalEvent(domainID, depositNonce, ProposalStatus.Passed, dataHash);
}
}
_proposals[nonceAndID][dataHash] = proposal;
if (proposal._status == ProposalStatus.Passed) {
executeProposal(domainID, depositNonce, data, resourceID, false);
}
}
最后 跨链桥的合约层是比较简单的,复杂的逻辑都在跨链逻辑层,常用的语言是Golang。