The Web3 项目实战--深入理解 DappLink Bridge 智能合约实现机制

一.协议概述

DappLink 跨链互操作协议是一个基于 ZKP 的任意链到任意链的数据和资产跨链协议,目前已经支持 EVM 系列链之间的数据和资产跨链。

1.重点协议设计

1.1.资产跨链

图像

1.2.数据跨链

图像

二.业务实现流程

1.资产质押

Relayer 每隔一个周期会在以太坊上自动创建质押池, 没有 cliam 资产的用户的资金继续流转到新的质押池,新用户质押将在下一期开启之后生效,创建质押池的代码如下:

function CompletePoolAndNew(
    Pool[] memory CompletePools
) external payable onlyRole(ReLayer) {
    for (uint256 i = 0; i < CompletePools.length; i++) {
        address _token = CompletePools[i].token;
        uint PoolIndex = Pools[_token].length - 1;
        Pools[_token][PoolIndex - 1].IsCompleted = true;
        if (PoolIndex - 1 != 0) {
            Pools[_token][PoolIndex - 1].TotalFee = FeePoolValue[_token];
            FeePoolValue[_token] = 0;
        }
        uint32 startTimes = Pools[_token][PoolIndex].endTimestamp;
        Pools[_token].push(
            Pool({
                startTimestamp: startTimes,
                endTimestamp: startTimes + periodTime,
                token: _token,
                TotalAmount: Pools[_token][PoolIndex].TotalAmount,
                TotalFee: 0,
                TotalFeeClaimed: 0,
                IsCompleted: false
            })
        );
        emit CompletePoolEvent(_token, PoolIndex);
    }
}

用户将自己的 Token 质押给以太坊上的 FundingPool,网络中每发生一笔交易质押者都会获取一定的收益

  • 质押方法,质押方法里面包含了质押 ETH 和 Token, Token 这里只含同质化代币,非同质化代币 NFT 这样的我们是通过 lock, unlock, mint, burn 的方式来实现的,不需要散户进行质押。
function DepositAndStaking(
    address _token,
    uint256 _amount
) public payable override whenNotPaused {
    if (msg.value > 0) {
        DepositAndStakingETH();
    } else if (_token == ContractsAddress.WETH) {
        DepositAndStakingWETH(_amount);
    } else if (IsSupportToken[_token]) {
        DepositAndStakingERC20(_token, _amount);
    }
}
  • ETH 质押,主要功能是用户把钱打入到合约,合约里面会记录用户的资金,做为未来奖励和提取本金的证明,代码如下:
function DepositAndStakingETH()
    public
    payable
    override
    nonReentrant
    whenNotPaused
{
    if (msg.value < MinStakeAmount[address(ContractsAddress.ETHAddress)]) {
        revert LessThanMinStakeAmount(
            MinStakeAmount[address(ContractsAddress.ETHAddress)],
            msg.value
        );
    }

    if (Pools[address(ContractsAddress.ETHAddress)].length == 0) {
        revert NewPoolIsNotCreate(1);
    }
    uint256 PoolIndex = Pools[address(ContractsAddress.ETHAddress)].length -
        1;
    /*if (
        Pools[address(ContractsAddress.ETHAddress)][PoolIndex].IsCompleted
    ) {
        revert PoolIsCompleted(PoolIndex);
    }*/
    if (
        Pools[address(ContractsAddress.ETHAddress)][PoolIndex]
            .startTimestamp > block.timestamp
    ) {
        Users[msg.sender].push(
            User({
                isWithdrawed: false,
                StartPoolId: PoolIndex,
                EndPoolId: 0,
                token: ContractsAddress.ETHAddress,
                Amount: msg.value
            })
        );
        Pools[address(ContractsAddress.ETHAddress)][PoolIndex]
            .TotalAmount += msg.value;
    } else {
        revert NewPoolIsNotCreate(PoolIndex + 1);
    }
    FundingPoolBalance[ContractsAddress.ETHAddress] += msg.value;
    emit StakingETHEvent(msg.sender, msg.value);
}
  • WETH 质押,主要功能是用户把钱打入到合约,合约里面会记录用户的资金,做为未来奖励和提取本金的证明,与其他 Token 不一样的是,质押 WETH 的用户,未来本金提取可以提取 ETH, 代码如下:
function DepositAndStakingWETH(
    uint256 amount
) public override nonReentrant whenNotPaused {
    if (amount < MinStakeAmount[address(ContractsAddress.WETH)]) {
        revert LessThanMinStakeAmount(
            MinStakeAmount[address(ContractsAddress.WETH)],
            amount
        );
    }

    IWETH(ContractsAddress.WETH).transferFrom(
        msg.sender,
        address(this),
        amount
    );

    if (Pools[address(ContractsAddress.WETH)].length == 0) {
        revert NewPoolIsNotCreate(1);
    }
    uint256 PoolIndex = Pools[address(ContractsAddress.WETH)].length - 1;
    /*if (Pools[address(ContractsAddress.WETH)][PoolIndex].IsCompleted) {
        revert PoolIsCompleted(PoolIndex);
    }*/
    if (
        Pools[address(ContractsAddress.WETH)][PoolIndex].startTimestamp >
        block.timestamp
    ) {
        Users[msg.sender].push(
            User({
                isWithdrawed: false,
                StartPoolId: PoolIndex,
                EndPoolId: 0,
                token: ContractsAddress.WETH,
                Amount: amount
            })
        );
        Pools[address(ContractsAddress.WETH)][PoolIndex]
            .TotalAmount += amount;
    } else {
        revert NewPoolIsNotCreate(PoolIndex + 1);
    }
    FundingPoolBalance[ContractsAddress.WETH] += amount;
    emit StakingWETHEvent(msg.sender, amount);
}
  • ERC20 质押:主要功能是用户把钱打入到合约,合约里面会记录用户的资金,做为未来奖励和提取本金的证明,代码如下:
function DepositAndStakingERC20(
    address _token,
    uint256 _amount
) public override nonReentrant whenNotPaused {
    if (!IsSupportToken[_token]) {
        revert TokenIsNotSupported(_token);
    }
    if (_amount < MinStakeAmount[_token]) {
        revert LessThanMinStakeAmount(MinStakeAmount[_token], _amount);
    }
    uint256 BalanceBefore = IERC20(_token).balanceOf(address(this));
    IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
    uint256 BalanceAfter = IERC20(_token).balanceOf(address(this));
    _amount = BalanceAfter - BalanceBefore;

    if (Pools[_token].length == 0) {
        revert NewPoolIsNotCreate(1);
    }
    uint256 PoolIndex = Pools[_token].length - 1;
    if (Pools[_token][PoolIndex].startTimestamp > block.timestamp) {
        Users[msg.sender].push(
            User({
                isWithdrawed: false,
                StartPoolId: PoolIndex,
                EndPoolId: 0,
                token: _token,
                Amount: _amount
            })
        );
        Pools[_token][PoolIndex].TotalAmount += _amount;
    } else {
        revert NewPoolIsNotCreate(PoolIndex + 1);
    }
    FundingPoolBalance[_token] += _amount;
    emit StarkingERC20Event(msg.sender, _token, _amount);
}

Relayer 的资金平衡程序按照一定规则将资金划转到其他的各个网络

  • Relayer 是一个链下服务,他会根据事先配置好的资金平衡规则,将资金划转到其他 ETH Layer2 上,资金平衡针对的是有关联的网络,例如:ETH 及其 Layer2, BTC 及其 Layer2。资金平衡规则的代码这里我们就不做讲解了。

2.资产跨链

用户将资金转入到源链的 FundingPool, Relayer 监听到这笔交易之后,生成交易证明,将交易证明丢到目标链上;Relayer 在目标链上帮助用户 Claim 资金, 交易证明有效之后,Relayer 从目标链的 FundingPool 将钱划转到用户的地址

2.1 ETH 资产跨链

  • ETH 跨链源链:用户把资金转到源链的资金池,对应方法为 BridgeInitiateETH;代码如下:
function BridgeInitiateETH(
    uint256 sourceChainId,
    uint256 destChainId,
    address to
) external payable returns (bool) {
    if (sourceChainId != block.chainid) {
        revert sourceChainIdError();
    }
    if (!IsSupportChainId(destChainId)) {
        revert ChainIdIsNotSupported(destChainId);
    }
    if (msg.value < MinTransferAmount) {
        revert LessThanMinTransferAmount(MinTransferAmount, msg.value);
    }
    FundingPoolBalance[ContractsAddress.ETHAddress] += msg.value;

    uint256 fee = (msg.value * PerFee) / 1_000_000;
    uint256 amount = msg.value - fee;
    FeePoolValue[ContractsAddress.ETHAddress] += fee;

    messageManager.sendMessage(block.chainid, destChainId, to, amount, fee);

    emit InitiateETH(sourceChainId, destChainId, msg.sender, to, amount);
    return true;
}
  • ETH 跨链目标链:Relayer 网络完成交易证明之后,将目标链的资金池的资金划转到用户指定的地址上;对应方法为 BridgeInitiateETH;代码如下:
function BridgeFinalizeETH(
    uint256 sourceChainId,
    uint256 destChainId,
    address to,
    uint256 amount,
    uint256 _fee,
    uint256 _nonce
) external payable onlyRole(ReLayer) returns (bool) {
    if (destChainId != block.chainid) {
        revert sourceChainIdError();
    }
    if (!IsSupportChainId(sourceChainId)) {
        revert ChainIdIsNotSupported(sourceChainId);
    }
    (bool _ret, ) = payable(to).call{value: amount}("");
    if (!_ret) {
        revert TransferETHFailed();
    }
    FundingPoolBalance[ContractsAddress.ETHAddress] -= amount;

    messageManager.claimMessage(
        sourceChainId,
        destChainId,
        to,
        _fee,
        amount,
        _nonce
    );

    emit FinalizeETH(sourceChainId, destChainId, address(this), to, amount);
    return true;
}

2.2 WETH 资产跨链

  • WETH 跨链源链:用户把资金转到源链的资金池,对应方法为 BridgeInitiateWETH;代码如下:
function BridgeInitiateWETH(
    uint256 sourceChainId,
    uint256 destChainId,
    address to,
    uint256 value
) external returns (bool) {
    if (sourceChainId != block.chainid) {
        revert sourceChainIdError();
    }
    if(sourceChainId == destChainId){
        revert sourceChainIsDestChainError();
    }
    if (!IsSupportChainId(destChainId)) {
        revert ChainIdNotSupported(destChainId);
    }

    IWETH WETH = IWETH(L2WETH());

    uint256 BalanceBefore = WETH.balanceOf(address(this));
    WETH.transferFrom(msg.sender, address(this), value);
    uint256 BalanceAfter = WETH.balanceOf(address(this));
    uint256 amount = BalanceAfter - BalanceBefore;
    if (amount < MinTransferAmount) {
        revert LessThanMinTransferAmount(MinTransferAmount, amount);
    }
    FundingPoolBalance[ContractsAddress.WETH] += amount;

    uint256 fee = (amount * PerFee) / 1_000_000;
    amount -= fee;
    FeePoolValue[ContractsAddress.WETH] += fee;

    messageManager.sendMessage(sourceChainId, destChainId, to, amount, fee);

    emit InitiateWETH(sourceChainId, destChainId, msg.sender, to, amount);

    return true;
}
  • WETH 跨链目标链:Relayer 网络完成交易证明之后,将目标链的资金池的资金划转到用户指定的地址上;对应方法为 BridgeInitiateWETH;代码如下:
function BridgeFinalizeWETH(
    uint256 sourceChainId,
    uint256 destChainId,
    address to,
    uint256 amount,
    uint256 _fee,
    uint256 _nonce
) external onlyRole(ReLayer) returns (bool) {
    if (destChainId != block.chainid) {
        revert sourceChainIdError();
    }
    if (!IsSupportChainId(sourceChainId)) {
        revert ChainIdIsNotSupported(sourceChainId);
    }

    IWETH WETH = IWETH(L2WETH());
    WETH.transfer(to, amount);
    FundingPoolBalance[ContractsAddress.WETH] -= amount;

    messageManager.claimMessage(
        sourceChainId,
        destChainId,
        to,
        _fee,
        amount,
        _nonce
    );

    emit FinalizeWETH(
        sourceChainId,
        destChainId,
        address(this),
        to,
        amount
    );
    return true;
}

2.3 ERC20 资产跨链

  • ERC20 跨链源链:用户把资金转到源链的资金池,对应方法为 BridgeInitiateERC20;代码如下:
function BridgeInitiateERC20(
    uint256 sourceChainId,
    uint256 destChainId,
    address to,
    address ERC20Address,
    uint256 value
) external returns (bool) {
    if (sourceChainId != block.chainid) {
        revert sourceChainIdError();
    }
    if (!IsSupportChainId(destChainId)) {
        revert ChainIdIsNotSupported(destChainId);
    }
    if (!IsSupportToken[ERC20Address]) {
        revert TokenIsNotSupported(ERC20Address);
    }

    uint256 BalanceBefore = IERC20(ERC20Address).balanceOf(address(this));
    IERC20(ERC20Address).safeTransferFrom(msg.sender, address(this), value);
    uint256 BalanceAfter = IERC20(ERC20Address).balanceOf(address(this));
    uint256 amount = BalanceAfter - BalanceBefore;
    FundingPoolBalance[ERC20Address] += value;
    uint256 fee = (amount * PerFee) / 1_000_000;
    amount -= fee;
    FeePoolValue[ERC20Address] += fee;

    messageManager.sendMessage(sourceChainId, destChainId, to, amount, fee);

    emit InitiateERC20(
        sourceChainId,
        destChainId,
        ERC20Address,
        msg.sender,
        to,
        amount
    );

    return true;
}
  • ERC20 跨链目标链:Relayer 网络完成交易证明之后,将目标链的资金池的资金划转到用户指定的地址上;对应方法为 BridgeFinalizeERC20;代码如下:
function BridgeFinalizeERC20(
    uint256 sourceChainId,
    uint256 destChainId,
    address to,
    address ERC20Address,
    uint256 amount,
    uint256 _fee,
    uint256 _nonce
) external onlyRole(ReLayer) returns (bool) {
    if (destChainId != block.chainid) {
        revert sourceChainIdError();
    }
    if (!IsSupportChainId(sourceChainId)) {
        revert ChainIdIsNotSupported(sourceChainId);
    }
    if (!IsSupportToken[ERC20Address]) {
        revert TokenIsNotSupported(ERC20Address);
    }
    IERC20(ERC20Address).safeTransfer(to, amount);
    FundingPoolBalance[ERC20Address] -= amount;

    messageManager.claimMessage(
        sourceChainId,
        destChainId,
        to,
        _fee,
        amount,
        _nonce
    );

    emit FinalizeERC20(
        sourceChainId,
        destChainId,
        ERC20Address,
        address(this),
        to,
        amount
    );
    return true;
}

3.奖励和本金提现

  • 用户可以随时提取自己的奖励和本金,若本期未结束用户就提取奖励和本金,用户只能获得截止到上一期的收益, 代码如下:
function WithdrawOrClaimBySimpleAsset(
    address _user,
    address _token,
    bool IsWithdraw
) internal {
    if (Pools[_token].length == 0) {
        revert NewPoolIsNotCreate(0);
    }
    for (uint256 index = 0; index < Users[_user].length; index++) {
        if (Users[_user][index].token == _token) {
            if (Users[_user][index].isWithdrawed) {
                continue;
            }

            uint256 EndPoolId = Pools[_token].length - 1;

            uint256 Reward = 0;
            uint256 Amount = Users[_user][index].Amount;
            uint256 startPoolId = Users[_user][index].StartPoolId;
            /*if (startPoolId > EndPoolId) {
                revert NoReward();
            }*/

            for (uint256 j = startPoolId; j < EndPoolId; j++) {
                uint256 _Reward = (Amount *
                    Pools[_token][j].TotalFee *
                    1e18) / Pools[_token][j].TotalAmount;
                Reward += _Reward / 1e18;
                Pools[_token][j].TotalFeeClaimed += _Reward;
            }
            //require(Reward > 0, "No Reward");
            Amount += Reward;
            Users[_user][index].isWithdrawed = true;
            if (IsWithdraw) {
                Pools[_token][EndPoolId].TotalAmount -= Users[_user][index]
                    .Amount;
                SendAssertToUser(_token, _user, Amount);
                if (Users[_user].length > 0) {
                    Users[_user][index] = Users[_user][
                        Users[_user].length - 1
                    ];
                    Users[_user].pop();
                    index--;
                }
                emit Withdraw(
                    _user,
                    startPoolId,
                    EndPoolId,
                    _token,
                    Amount - Reward,
                    Reward
                );
            } else {
                Users[_user][index].StartPoolId = EndPoolId;
                SendAssertToUser(_token, _user, Reward);
                emit ClaimReward(
                    _user,
                    startPoolId,
                    EndPoolId,
                    _token,
                    Reward
                );
            }
        }
    }
}

4.数据跨链

  • 源链发送数据跨链的交易, Relayer 监听到这笔交易之后,生成交易证明,将交易证明丢到目标链上
  • Relayer 在目标链上消费数据, 交易证明有效之后,Relayer 在目标帮助数据消费

三. 总结

  • DappLink 合约项目地址:https://github.com/eniac-x-labs/bridge-contracts
  • DappLink 合约事件监听器:https://github.com/eniac-x-labs/acorus
  • DappLink Relayer 网络部分代码:https://github.com/eniac-x-labs/selaginella

留言
全部评论(0)