friend_tech 第一笔交易只能买 1 个key?
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>群里有人说friend tech 第一笔交易只能自己购买1笔?</p><p>我当时就反驳了他,毕竟我也算看过 friend tech 合约代码的人,合约没有限制只能买 1 笔啊</p><h2 id="废话少说,直接看代码"><a href="#废话少说,直接看代码" class="headerlink" title="废话少说,直接看代码"></a>废话少说,直接看代码</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">buyShares</span>(<span class="params">address sharesSubject, uint256 amount</span>) public payable {</span><br><span class="line"> uint256 supply = sharesSupply[sharesSubject];</span><br><span class="line"> <span class="built_in">require</span>(supply > <span class="number">0</span> || sharesSubject == msg.<span class="property">sender</span>, <span class="string">"Only the shares' subject can buy the first share"</span>);</span><br><span class="line"> uint256 price = <span class="title function_">getPrice</span>(supply, amount);</span><br><span class="line"> uint256 protocolFee = price * protocolFeePercent / <span class="number">1</span> ether;</span><br><span class="line"> uint256 subjectFee = price * subjectFeePercent / <span class="number">1</span> ether;</span><br><span class="line"> <span class="built_in">require</span>(msg.<span class="property">value</span> >= price + protocolFee + subjectFee, <span class="string">"Insufficient payment"</span>);</span><br><span class="line"> sharesBalance[sharesSubject][msg.<span class="property">sender</span>] = sharesBalance[sharesSubject][msg.<span class="property">sender</span>] + amount;</span><br><span class="line"> sharesSupply[sharesSubject] = supply + amount;</span><br><span class="line"> emit <span class="title class_">Trade</span>(msg.<span class="property">sender</span>, sharesSubject, <span class="literal">true</span>, amount, price, protocolFee, subjectFee, supply + amount);</span><br><span class="line"> (bool success1,) = protocolFeeDestination.<span class="property">call</span>{<span class="attr">value</span>: protocolFee}(<span class="string">""</span>);</span><br><span class="line"> (bool success2,) = sharesSubject.<span class="property">call</span>{<span class="attr">value</span>: subjectFee}(<span class="string">""</span>);</span><br><span class="line"> <span class="built_in">require</span>(success1 && success2, <span class="string">"Unable to send funds"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>一看代码很简单,唯一限制的就这一句</p><p><code>require(supply > 0 || sharesSubject == msg.sender, "Only the shares' subject can buy the first share");</code></p><p>这句话的意思 新注册用户,只能自己买入第一笔. 也没有限制买多少key啊…</p><h2 id="没那么简单"><a href="#没那么简单" class="headerlink" title="没那么简单"></a>没那么简单</h2><p>那别人为啥还言之凿凿说确实只能买 1 个key 呢?</p><p>严谨一点直接用forge模拟买买看就知道了.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> {<span class="title class_">TestHarness</span>} <span class="keyword">from</span> <span class="string">"../../TestHarness.sol"</span>;</span><br><span class="line"></span><br><span class="line">interface iFriendtech {</span><br><span class="line"> <span class="keyword">function</span> <span class="title function_">buyShares</span>(<span class="params">address sharesSubject, uint256 amount</span>) external payable;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">contract fuck_friend_tech is <span class="title class_">TestHarness</span> {</span><br><span class="line"> iFriendtech friendtech = <span class="title function_">iFriendtech</span>(<span class="number">0xCF205808Ed36593aa40a44F10c7f7C2F67d4A4d4</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">function</span> <span class="title function_">setUp</span>(<span class="params"></span>) external {</span><br><span class="line"> cheat.<span class="title function_">createSelectFork</span>(<span class="string">"base"</span>, <span class="number">4_119_770</span>);</span><br><span class="line"> cheat.<span class="title function_">deal</span>(<span class="title function_">address</span>(<span class="variable language_">this</span>), <span class="number">100</span> ether);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">function</span> <span class="title function_">test_attack</span>(<span class="params"></span>) external {</span><br><span class="line"> friendtech.<span class="property">buyShares</span>{<span class="attr">value</span>: <span class="number">100</span> ether}(<span class="title function_">address</span>(<span class="variable language_">this</span>), <span class="number">2</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="title function_">receive</span>() external payable {}</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>你别说,还真失败了…报错为 <code>[FAIL. Reason: Arithmetic over/underflow] test_attack() (gas: 3252)</code></p><p>显示溢出报错.肯定是哪里做运算出错了</p><p>再回头看<code>buyShares</code>的代码,唯一有疑点的地方就是<code>getPrice</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getPrice</span>(<span class="params">uint256 supply, uint256 amount</span>) public view <span class="title function_">returns</span> (uint256) {</span><br><span class="line"> uint256 sum1 = supply == <span class="number">0</span> ? <span class="number">0</span> : (supply - <span class="number">1</span>) * (supply) * (<span class="number">2</span> * (supply - <span class="number">1</span>) + <span class="number">1</span>) / <span class="number">6</span>;</span><br><span class="line"> uint256 sum2 = supply == <span class="number">0</span> && amount == <span class="number">1</span></span><br><span class="line"> ? <span class="number">0</span></span><br><span class="line"> : (supply - <span class="number">1</span> + amount) * (supply + amount) * (<span class="number">2</span> * (supply - <span class="number">1</span> + amount) + <span class="number">1</span>) / <span class="number">6</span>;</span><br><span class="line"></span><br><span class="line"> uint256 summation = sum2 - sum1;</span><br><span class="line"> <span class="keyword">return</span> summation * <span class="number">1</span> ether / <span class="number">16_000</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>出现问题在sum2… supply-1+amount这边就出错了…毕竟 solidity 语言有点傻,0-1=-1就直接溢出了…</p><p>好吧,原来不能第一次买多个的原因是 团队写 bug 了…改正也很简单,先+后-就可以了,这也算 solidity 开发常见的问题了,<strong>要先加后减,先乘后除</strong></p><p><code>(supply - 1 + amount) * (supply + amount) * (2 * (supply - 1 + amount) + 1) / 6;</code><br><code>(supply + amount - 1) * (supply + amount) * (2 * (supply + amount - 1) + 1) / 6;</code></p><h2 id="getPrice-分析"><a href="#getPrice-分析" class="headerlink" title="getPrice 分析"></a>getPrice 分析</h2><p>再回头看getPrice的代码.<br>估计第一眼看过去的都会比较迷糊,这到底是干啥了…当然数学大神一下就能看出来这是平方和公式</p><p>我们可以借助 ChatGPT</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Q: (supply - 1) * (supply) * (2 * (supply - 1) + 1) / 6 这是什么公式?</span><br><span class="line"></span><br><span class="line">A: </span><br><span class="line">这个公式是用来计算一个整数变量(通常表示为"supply")的平方和平方和的一种表示方式。公式的具体步骤如下:</span><br><span class="line"></span><br><span class="line">1. 先将"supply"的值减去1,即"(supply - 1)"。</span><br><span class="line">2. 然后将该值与"supply"相乘,即"(supply - 1) * supply"。</span><br><span class="line">3. 接下来,将2乘以"(supply - 1)",即"2 * (supply - 1)"。</span><br><span class="line">4. 将上一步骤的结果加1,即"2 * (supply - 1) + 1"。</span><br><span class="line">5. 最后,将前三步骤的结果相乘,并将其除以6,即"(supply - 1) * supply * (2 * (supply - 1) + 1) / 6"。</span><br><span class="line"></span><br><span class="line">这个公式可以用来计算一系列连续整数的平方和,其中"supply"代表整数的上限。这种形式的平方和通常在数学和统计学中有多种应用,比如计算方差、概率分布等。</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>知道这是平方求和公式就简单了,这段代码就很好理解了<br><code>((supply-1+amount)的平方和 - (supply-1)的平方和) / 16000</code></p><p><br /><strong>本文作者</strong>:高金<br /><strong>本文地址</strong>: <a href="https://igaojin.me/2023/09/19/friend-tech-%E7%AC%AC%E4%B8%80%E7%AC%94%E4%BA%A4%E6%98%93%E5%8F%AA%E8%83%BD%E4%B9%B0-1-%E4%B8%AAkey/">https://igaojin.me/2023/09/19/friend-tech-第一笔交易只能买-1-个key/</a> <br /><strong>版权声明</strong>:转载请注明出处!</p><div id="gitalk-container"></div><script src="https://cdn.bootcss.com/blueimp-md5/2.12.0/js/md5.min.js"></script><link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css"><script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script><script>var gitalkConfig = {"clientID":"935e92a5333436856348","clientSecret":"e655566eaf920d216ec6283978d67874bf0850a6","repo":"jin10086.github.io","owner":"jin10086","admin":["jin10086"],"distractionFreeMode":false,"id":"page.date","createIssueManually":false}; gitalkConfig.id = md5(location.pathname);var gitalk = new Gitalk(gitalkConfig); gitalk.render("gitalk-container"); </script>