<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>bugWriter</title>
        <link>https://paragraph.com/@bugwriter</link>
        <description>male</description>
        <lastBuildDate>Thu, 23 Jul 2026 01:22:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>bugWriter</title>
            <url>https://storage.googleapis.com/papyrus_images/4be7d7e6c8561371202082586634bbe5af5a234953507a6041f37c4cf37b58e3.webp</url>
            <link>https://paragraph.com/@bugwriter</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Meter Bridge && Qubit Bridge]]></title>
            <link>https://paragraph.com/@bugwriter/meter-bridge-qubit-bridge</link>
            <guid>iSXORXVgLWS3ZAd6RgOQ</guid>
            <pubDate>Fri, 18 Feb 2022 03:39:34 GMT</pubDate>
            <description><![CDATA[交易hash： 参考链接： https://twitter.com/peckshield/status/1490121762847092736?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1490121762847092736%7Ctwgr%5E%7Ctwcon%5Es1_&ref_url=https%3A%2F%2Fcdn.embedly.com%2Fwidgets%2Fmedia.html%3Ftype%3Dtext2Fhtmlkey%3Dd04bfffea46d4aeda930ec88cc64b87cschema%3Dtwitterurl%3Dhttps3A%2F%2Ftwitter.com%2Fpeckshield%2Fstatus%2F1490121762847092736image%3Dhttps3A%2F%2Fi.embed.ly%2F1%2Fimage3Furl3Dhttps253A252F252Fabs.twimg.com252Ferrors252Flogo46x38.png26key3D4fce05...]]></description>
            <content:encoded><![CDATA[<p>交易hash：</p><p>参考链接：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/peckshield/status/1490121762847092736?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1490121762847092736%7Ctwgr%5E%7Ctwcon%5Es1_&amp;ref_url=https%3A%2F%2Fcdn.embedly.com%2Fwidgets%2Fmedia.html%3Ftype%3Dtext2Fhtmlkey%3Dd04bfffea46d4aeda930ec88cc64b87cschema%3Dtwitterurl%3Dhttps3A%2F%2Ftwitter.com%2Fpeckshield%2Fstatus%2F1490121762847092736image%3Dhttps3A%2F%2Fi.embed.ly%2F1%2Fimage3Furl3Dhttps253A252F252Fabs.twimg.com252Ferrors252Flogo46x38.png26key3D4fce0568f2ce49e8b54624ef71a8a5bd">https://twitter.com/peckshield/status/1490121762847092736?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1490121762847092736%7Ctwgr%5E%7Ctwcon%5Es1_&amp;ref_url=https%3A%2F%2Fcdn.embedly.com%2Fwidgets%2Fmedia.html%3Ftype%3Dtext2Fhtmlkey%3Dd04bfffea46d4aeda930ec88cc64b87cschema%3Dtwitterurl%3Dhttps3A%2F%2Ftwitter.com%2Fpeckshield%2Fstatus%2F1490121762847092736image%3Dhttps3A%2F%2Fi.embed.ly%2F1%2Fimage3Furl3Dhttps253A252F252Fabs.twimg.com252Ferrors252Flogo46x38.png26key3D4fce0568f2ce49e8b54624ef71a8a5bd</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/meterio/chainbridge-solidity-v1.0.0-eth/tree/master/deployed_0421/merged">chainbridge-solidity-v1.0.0-eth/deployed_0421/merged at master · meterio/chainbridge-solidity-v1.0.0-eth</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/chainsafe-systems/breaking-down-the-meter-io-hack-a46a389e7ae4">Breaking down the Meter hack</a></p><p>错误原因：</p><p>产生错误的根本原因是：meter中针对deposit和depositETH，emit了相同的事件。但是在depositETH中，将ETH 包装成WETH后马上转给了handler，导致与deposit方法里对于ERC20的处理方式不一致，从而使得handler里面针对depositETH进行特殊处理。</p><p>即：跨链桥的逻辑应该是</p><p>用户→ 桥 deposit → Handler: transferFrom(burn/lock) → emit Deposit</p><p>用户→ 桥 depositETH: transfer WETH→ Handler (do nothing) → emit Deposit</p><p>⇒</p><p>用户 → 桥 deposit → Handler: do nothing</p><p>根本逻辑错误在于：handler里 if (tokenAddress != _wtokenAddress) 导致</p><pre data-type="codeBlock" text="Handler:
function deposit(
        bytes32 resourceID,
        uint8   destinationChainID,
        uint64  depositNonce,
        address depositer,
        bytes   calldata data
    ) external override onlyBridge {
        bytes   memory recipientAddress;
        uint256        amount;
        uint256        lenRecipientAddress;

        assembly {

            amount := calldataload(0xC4)

            recipientAddress := mload(0x40)
            lenRecipientAddress := calldataload(0xE4)
            mstore(0x40, add(0x20, add(recipientAddress, lenRecipientAddress)))

            calldatacopy(
                recipientAddress, // copy to destinationRecipientAddress
                0xE4, // copy from calldata @ 0x104
                sub(calldatasize(), 0xE) // copy size (calldatasize - 0x104)
            )
        }

        address tokenAddress = _resourceIDToTokenContractAddress[resourceID];
        require(_contractWhitelist[tokenAddress], &quot;provided tokenAddress is not whitelisted&quot;);
        
        // ether case, the weth already in handler, do nothing
        if (tokenAddress != _wtokenAddress) {
            if (_burnList[tokenAddress]) {
                burnERC20(tokenAddress, depositer, amount);
            } else {
                lockERC20(tokenAddress, depositer, address(this), amount);
            }
        }

        _depositRecords[destinationChainID][depositNonce] = DepositRecord(
            tokenAddress,
            uint8(lenRecipientAddress),
            destinationChainID,
            resourceID,
            recipientAddress,
            depositer,
            amount
        );
    }
"><code>Handler:
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deposit</span>(<span class="hljs-params">
        <span class="hljs-keyword">bytes32</span> resourceID,
        <span class="hljs-keyword">uint8</span>   destinationChainID,
        <span class="hljs-keyword">uint64</span>  depositNonce,
        <span class="hljs-keyword">address</span> depositer,
        <span class="hljs-keyword">bytes</span>   <span class="hljs-keyword">calldata</span> data
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title">onlyBridge</span> </span>{
        <span class="hljs-keyword">bytes</span>   <span class="hljs-keyword">memory</span> recipientAddress;
        <span class="hljs-keyword">uint256</span>        amount;
        <span class="hljs-keyword">uint256</span>        lenRecipientAddress;

        <span class="hljs-keyword">assembly</span> {

            amount <span class="hljs-operator">:=</span> <span class="hljs-built_in">calldataload</span>(<span class="hljs-number">0xC4</span>)

            recipientAddress <span class="hljs-operator">:=</span> <span class="hljs-built_in">mload</span>(<span class="hljs-number">0x40</span>)
            lenRecipientAddress <span class="hljs-operator">:=</span> <span class="hljs-built_in">calldataload</span>(<span class="hljs-number">0xE4</span>)
            <span class="hljs-built_in">mstore</span>(<span class="hljs-number">0x40</span>, <span class="hljs-built_in">add</span>(<span class="hljs-number">0x20</span>, <span class="hljs-built_in">add</span>(recipientAddress, lenRecipientAddress)))

            <span class="hljs-built_in">calldatacopy</span>(
                recipientAddress, <span class="hljs-comment">// copy to destinationRecipientAddress</span>
                <span class="hljs-number">0xE4</span>, <span class="hljs-comment">// copy from calldata @ 0x104</span>
                <span class="hljs-built_in">sub</span>(<span class="hljs-built_in">calldatasize</span>(), <span class="hljs-number">0xE</span>) <span class="hljs-comment">// copy size (calldatasize - 0x104)</span>
            )
        }

        <span class="hljs-keyword">address</span> tokenAddress <span class="hljs-operator">=</span> _resourceIDToTokenContractAddress[resourceID];
        <span class="hljs-built_in">require</span>(_contractWhitelist[tokenAddress], <span class="hljs-string">"provided tokenAddress is not whitelisted"</span>);
        
        <span class="hljs-comment">// ether case, the weth already in handler, do nothing</span>
        <span class="hljs-keyword">if</span> (tokenAddress <span class="hljs-operator">!</span><span class="hljs-operator">=</span> _wtokenAddress) {
            <span class="hljs-keyword">if</span> (_burnList[tokenAddress]) {
                burnERC20(tokenAddress, depositer, amount);
            } <span class="hljs-keyword">else</span> {
                lockERC20(tokenAddress, depositer, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), amount);
            }
        }

        _depositRecords[destinationChainID][depositNonce] <span class="hljs-operator">=</span> DepositRecord(
            tokenAddress,
            <span class="hljs-keyword">uint8</span>(lenRecipientAddress),
            destinationChainID,
            resourceID,
            recipientAddress,
            depositer,
            amount
        );
    }
</code></pre><p>当用户的传入的调用参数如下时：</p><pre data-type="codeBlock" text="Function: deposit(uint8 destinationChainID, bytes32 resourceID, bytes data)

MethodID: 0x05e2ca17
[0]:  0000000000000000000000000000000000000000000000000000000000000001 //destinationChainID
[1]:  0000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc201 //resourceID
[2]:  0000000000000000000000000000000000000000000000000000000000000060 //offset
[3]:  0000000000000000000000000000000000000000000000000000000000000054 //len
[4]:  000000000000000000000000000000000000000000000016e77c77f5de41f3a4 //amount
[5]:  0000000000000000000000000000000000000000000000000000000000000014 //addr len 0x14=20
[6]:  8d3d13cac607b7297ff61a5e1e71072758af4d01000000000000000000000000 //receipient addr
"><code>Function: deposit(uint8 destinationChainID, bytes32 resourceID, bytes data)

MethodID: 0x05e2ca17
[<span class="hljs-symbol">0</span>]:  <span class="hljs-link">0000000000000000000000000000000000000000000000000000000000000001 //destinationChainID</span>
[<span class="hljs-symbol">1</span>]:  <span class="hljs-link">0000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc201 //resourceID</span>
[<span class="hljs-symbol">2</span>]:  <span class="hljs-link">0000000000000000000000000000000000000000000000000000000000000060 //offset</span>
[<span class="hljs-symbol">3</span>]:  <span class="hljs-link">0000000000000000000000000000000000000000000000000000000000000054 //len</span>
[<span class="hljs-symbol">4</span>]:  <span class="hljs-link">000000000000000000000000000000000000000000000016e77c77f5de41f3a4 //amount</span>
[<span class="hljs-symbol">5</span>]:  <span class="hljs-link">0000000000000000000000000000000000000000000000000000000000000014 //addr len 0x14=20</span>
[<span class="hljs-symbol">6</span>]:  <span class="hljs-link">8d3d13cac607b7297ff61a5e1e71072758af4d01000000000000000000000000 //receipient addr</span>
</code></pre><p>首先在Handler中（0xde4fC7C3C5E7bE3F16506FcC790a8D93f8Ca0b40），根据wtokenAddress查找到对应的resouceID：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b2bd9451d95db0b6c21b1b5888065cf041e92bf788144178b1b767c96aed4ddf.png" alt="resource ID" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">resource ID</figcaption></figure><p>然后构造上述的一个交易数据即可。</p><p>💡 Qubit</p><p>参考链接：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/peckshield/status/1486841239450255362">https://twitter.com/peckshield/status/1486841239450255362</a></p><p>错误原因：</p><p>用户→Bridge: deposit (resourceID → ETH) → Handler: deposit (tokenAddress = 0)</p><p>当handler中，deposit tokenAddr=0, 其调用safeTransferFrom时，其会直接调用STOP，返回true，而不是revert或者false。</p><p>即：</p><pre data-type="codeBlock" text="function safeTransfer(
        address token,
        address to,
        uint value
    ) internal {
        // bytes4(keccak256(bytes(&apos;transfer(address,uint256)&apos;)));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success &amp;&amp; (data.length == 0 || abi.decode(data, (bool))), &quot;!safeTransfer&quot;);
    }
当token不是一个合约地址时，比如一个EOA地址，其调用的call仍然会成功，返回success！
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">safeTransfer</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> token,
        <span class="hljs-keyword">address</span> to,
        <span class="hljs-keyword">uint</span> value
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
        <span class="hljs-comment">// bytes4(keccak256(bytes('transfer(address,uint256)')));</span>
        (<span class="hljs-keyword">bool</span> success, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data) <span class="hljs-operator">=</span> token.<span class="hljs-built_in">call</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodeWithSelector</span>(<span class="hljs-number">0xa9059cbb</span>, to, value));
        <span class="hljs-built_in">require</span>(success <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> (data.<span class="hljs-built_in">length</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data, (<span class="hljs-keyword">bool</span>))), <span class="hljs-string">"!safeTransfer"</span>);
    }
当token不是一个合约地址时，比如一个EOA地址，其调用的call仍然会成功，返回success！
</code></pre><pre data-type="codeBlock" text="Bridge:
function deposit(uint8 destinationDomainID, bytes32 resourceID, bytes calldata data) external payable notPaused {
        require(msg.value == fee, &quot;QBridge: invalid fee&quot;);

        address handler = resourceIDToHandlerAddress[resourceID];
        require(handler != address(0), &quot;QBridge: invalid resourceID&quot;);

        uint64 depositNonce = ++_depositCounts[destinationDomainID];

        IQBridgeHandler(handler).deposit(resourceID, msg.sender, data);
        emit Deposit(destinationDomainID, resourceID, depositNonce, msg.sender, data);
    }

    function depositETH(uint8 destinationDomainID, bytes32 resourceID, bytes calldata data) external payable notPaused {
        uint option;
        uint amount;
        (option, amount) = abi.decode(data, (uint, uint));

        require(msg.value == amount.add(fee), &quot;QBridge: invalid fee&quot;);

        address handler = resourceIDToHandlerAddress[resourceID];
        require(handler != address(0), &quot;QBridge: invalid resourceID&quot;);

        uint64 depositNonce = ++_depositCounts[destinationDomainID];

        IQBridgeHandler(handler).depositETH{value:amount}(resourceID, msg.sender, data);
        emit Deposit(destinationDomainID, resourceID, depositNonce, msg.sender, data);
    }
Handler:
function deposit(bytes32 resourceID, address depositer, bytes calldata data) external override onlyBridge {
        uint option;
        uint amount;
        (option, amount) = abi.decode(data, (uint, uint));

        address tokenAddress = resourceIDToTokenContractAddress[resourceID];
        require(contractWhitelist[tokenAddress], &quot;provided tokenAddress is not whitelisted&quot;);

        if (burnList[tokenAddress]) {
            require(amount &gt;= withdrawalFees[resourceID], &quot;less than withdrawal fee&quot;);
            QBridgeToken(tokenAddress).burnFrom(depositer, amount);
        } else {
            require(amount &gt;= minAmounts[resourceID][option], &quot;less than minimum amount&quot;);
            tokenAddress.safeTransferFrom(depositer, address(this), amount);
        }
    }

    function depositETH(bytes32 resourceID, address depositer, bytes calldata data) external payable override onlyBridge {
        uint option;
        uint amount;
        (option, amount) = abi.decode(data, (uint, uint));
        require(amount == msg.value);

        address tokenAddress = resourceIDToTokenContractAddress[resourceID];
        require(contractWhitelist[tokenAddress], &quot;provided tokenAddress is not whitelisted&quot;);

        require(amount &gt;= minAmounts[resourceID][option], &quot;less than minimum amount&quot;);
    }
"><code>Bridge:
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deposit</span>(<span class="hljs-params"><span class="hljs-keyword">uint8</span> destinationDomainID, <span class="hljs-keyword">bytes32</span> resourceID, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> data</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> <span class="hljs-title">notPaused</span> </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> fee, <span class="hljs-string">"QBridge: invalid fee"</span>);

        <span class="hljs-keyword">address</span> handler <span class="hljs-operator">=</span> resourceIDToHandlerAddress[resourceID];
        <span class="hljs-built_in">require</span>(handler <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-string">"QBridge: invalid resourceID"</span>);

        <span class="hljs-keyword">uint64</span> depositNonce <span class="hljs-operator">=</span> <span class="hljs-operator">+</span><span class="hljs-operator">+</span>_depositCounts[destinationDomainID];

        IQBridgeHandler(handler).deposit(resourceID, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, data);
        <span class="hljs-keyword">emit</span> Deposit(destinationDomainID, resourceID, depositNonce, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, data);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">depositETH</span>(<span class="hljs-params"><span class="hljs-keyword">uint8</span> destinationDomainID, <span class="hljs-keyword">bytes32</span> resourceID, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> data</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> <span class="hljs-title">notPaused</span> </span>{
        <span class="hljs-keyword">uint</span> option;
        <span class="hljs-keyword">uint</span> amount;
        (option, amount) <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data, (<span class="hljs-keyword">uint</span>, <span class="hljs-keyword">uint</span>));

        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> amount.add(fee), <span class="hljs-string">"QBridge: invalid fee"</span>);

        <span class="hljs-keyword">address</span> handler <span class="hljs-operator">=</span> resourceIDToHandlerAddress[resourceID];
        <span class="hljs-built_in">require</span>(handler <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-string">"QBridge: invalid resourceID"</span>);

        <span class="hljs-keyword">uint64</span> depositNonce <span class="hljs-operator">=</span> <span class="hljs-operator">+</span><span class="hljs-operator">+</span>_depositCounts[destinationDomainID];

        IQBridgeHandler(handler).depositETH{<span class="hljs-built_in">value</span>:amount}(resourceID, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, data);
        <span class="hljs-keyword">emit</span> Deposit(destinationDomainID, resourceID, depositNonce, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, data);
    }
Handler:
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deposit</span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span> resourceID, <span class="hljs-keyword">address</span> depositer, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> data</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title">onlyBridge</span> </span>{
        <span class="hljs-keyword">uint</span> option;
        <span class="hljs-keyword">uint</span> amount;
        (option, amount) <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data, (<span class="hljs-keyword">uint</span>, <span class="hljs-keyword">uint</span>));

        <span class="hljs-keyword">address</span> tokenAddress <span class="hljs-operator">=</span> resourceIDToTokenContractAddress[resourceID];
        <span class="hljs-built_in">require</span>(contractWhitelist[tokenAddress], <span class="hljs-string">"provided tokenAddress is not whitelisted"</span>);

        <span class="hljs-keyword">if</span> (burnList[tokenAddress]) {
            <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">></span><span class="hljs-operator">=</span> withdrawalFees[resourceID], <span class="hljs-string">"less than withdrawal fee"</span>);
            QBridgeToken(tokenAddress).burnFrom(depositer, amount);
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">></span><span class="hljs-operator">=</span> minAmounts[resourceID][option], <span class="hljs-string">"less than minimum amount"</span>);
            tokenAddress.safeTransferFrom(depositer, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), amount);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">depositETH</span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span> resourceID, <span class="hljs-keyword">address</span> depositer, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> data</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title">onlyBridge</span> </span>{
        <span class="hljs-keyword">uint</span> option;
        <span class="hljs-keyword">uint</span> amount;
        (option, amount) <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data, (<span class="hljs-keyword">uint</span>, <span class="hljs-keyword">uint</span>));
        <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>);

        <span class="hljs-keyword">address</span> tokenAddress <span class="hljs-operator">=</span> resourceIDToTokenContractAddress[resourceID];
        <span class="hljs-built_in">require</span>(contractWhitelist[tokenAddress], <span class="hljs-string">"provided tokenAddress is not whitelisted"</span>);

        <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">></span><span class="hljs-operator">=</span> minAmounts[resourceID][option], <span class="hljs-string">"less than minimum amount"</span>);
    }
</code></pre><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/tx/0x3dfa33b5c6150bf3d64f49cb97eba351f99e4dff7119ef458e40f51160bf77ec/advanced">https://etherscan.io/tx/0x3dfa33b5c6150bf3d64f49cb97eba351f99e4dff7119ef458e40f51160bf77ec/advanced</a></p><p>攻击者的调用参数为：</p><pre data-type="codeBlock" text="Function: deposit(uint8 destinationDomainID, bytes32 resourceID, bytes data)

MethodID: 0x05e2ca17
[0]:  0000000000000000000000000000000000000000000000000000000000000001 //destination
[1]:  00000000000000000000002f422fe9ea622049d6f73f81a906b9b8cff03b7f01 //resource
[2]:  0000000000000000000000000000000000000000000000000000000000000060 //offset
[3]:  0000000000000000000000000000000000000000000000000000000000000060 //len
[4]:  0000000000000000000000000000000000000000000000000000000000000069 //option
[5]:  00000000000000000000000000000000000000000000021e0c0013070adc0000 //amount
[6]:  000000000000000000000000d01ae1a708614948b2b5e0b7ab5be6afa01325c7 //receipient
"><code>Function: deposit(uint8 destinationDomainID, bytes32 resourceID, bytes data)

MethodID: 0x05e2ca17
[<span class="hljs-symbol">0</span>]:  <span class="hljs-link">0000000000000000000000000000000000000000000000000000000000000001 //destination</span>
[<span class="hljs-symbol">1</span>]:  <span class="hljs-link">00000000000000000000002f422fe9ea622049d6f73f81a906b9b8cff03b7f01 //resource</span>
[<span class="hljs-symbol">2</span>]:  <span class="hljs-link">0000000000000000000000000000000000000000000000000000000000000060 //offset</span>
[<span class="hljs-symbol">3</span>]:  <span class="hljs-link">0000000000000000000000000000000000000000000000000000000000000060 //len</span>
[<span class="hljs-symbol">4</span>]:  <span class="hljs-link">0000000000000000000000000000000000000000000000000000000000000069 //option</span>
[<span class="hljs-symbol">5</span>]:  <span class="hljs-link">00000000000000000000000000000000000000000000021e0c0013070adc0000 //amount</span>
[<span class="hljs-symbol">6</span>]:  <span class="hljs-link">000000000000000000000000d01ae1a708614948b2b5e0b7ab5be6afa01325c7 //receipient</span>
</code></pre><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4c0f82634d33eb04b8732d433081b77559543a78d72bb5d70b912ece57bf4a39.png" alt="resource ID" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">resource ID</figcaption></figure><p>quibit被盗的根本原因其实在于：</p><p>他没有使用Openzeppelin的safeERC20合约，而是自己实现了一个版本的safeERC20. 但是在它自己实现的safeERC20合约里面的safeTransferFrom方法里有bug，没有检查token必须是合约地址，而不是EOA。</p><p>在Openzeppelin则做了相应的检查。</p><pre data-type="codeBlock" text="function safeTransferFrom(
        address token,
        address from,
        address to,
        uint value
    ) internal {
        // bytes4(keccak256(bytes(&apos;transferFrom(address,address,uint256)&apos;)));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(success &amp;&amp; (data.length == 0 || abi.decode(data, (bool))), &quot;!safeTransferFrom&quot;);
    }
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">safeTransferFrom</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> token,
        <span class="hljs-keyword">address</span> <span class="hljs-keyword">from</span>,
        <span class="hljs-keyword">address</span> to,
        <span class="hljs-keyword">uint</span> value
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
        <span class="hljs-comment">// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));</span>
        (<span class="hljs-keyword">bool</span> success, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data) <span class="hljs-operator">=</span> token.<span class="hljs-built_in">call</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodeWithSelector</span>(<span class="hljs-number">0x23b872dd</span>, <span class="hljs-keyword">from</span>, to, value));
        <span class="hljs-built_in">require</span>(success <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> (data.<span class="hljs-built_in">length</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> <span class="hljs-operator">|</span><span class="hljs-operator">|</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data, (<span class="hljs-keyword">bool</span>))), <span class="hljs-string">"!safeTransferFrom"</span>);
    }
</code></pre><pre data-type="codeBlock" text="function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
        require(address(this).balance &gt;= value, &quot;Address: insufficient balance for call&quot;);
        require(isContract(target), &quot;Address: call to non-contract&quot;);

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: value }(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">functionCallWithValue</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> target, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data, <span class="hljs-keyword">uint256</span> value, <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> errorMessage</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span></span>) </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span> <span class="hljs-operator">></span><span class="hljs-operator">=</span> value, <span class="hljs-string">"Address: insufficient balance for call"</span>);
        <span class="hljs-built_in">require</span>(isContract(target), <span class="hljs-string">"Address: call to non-contract"</span>);

        <span class="hljs-comment">// solhint-disable-next-line avoid-low-level-calls</span>
        (<span class="hljs-keyword">bool</span> success, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> returndata) <span class="hljs-operator">=</span> target.<span class="hljs-built_in">call</span>{ <span class="hljs-built_in">value</span>: value }(data);
        <span class="hljs-keyword">return</span> _verifyCallResult(success, returndata, errorMessage);
    }
</code></pre><p>进一步思考</p>]]></content:encoded>
            <author>bugwriter@newsletter.paragraph.com (bugWriter)</author>
        </item>
    </channel>
</rss>