{"componentChunkName":"component---src-templates-post-template-js","path":"/posts/kinesis-labs/dynamic-fees-postmortem","result":{"data":{"markdownRemark":{"id":"c9065bc6-d981-548b-80aa-77b4ba7c48c7","html":"<h1 id=\"kinesis-dynamic-fees-postmortem\" style=\"position:relative;\"><a href=\"#kinesis-dynamic-fees-postmortem\" aria-label=\"kinesis dynamic fees postmortem permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Kinesis Dynamic Fees Postmortem</h1>\n<p>Contact Information</p>\n<p>Telegram(Preferred): @kinesislabs</p>\n<p>Email: team@kinesislabs.co</p>\n<p>Discord: Dr.Kinesis#0157</p>\n<h1 id=\"key-points\" style=\"position:relative;\"><a href=\"#key-points\" aria-label=\"key points permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Key Points</h1>\n<ul>\n<li>All user funds are safe</li>\n<li>The issue here is incorrect arthimetic leading to safemath reverting when attemping to swap tokens, meaning swaps are disabled. However, users can still unstake and withdraw from the pools.</li>\n<li>The plan is to set <code class=\"language-text\">rewardsPerSecond</code> to 0 on the three current Simple Rewarders, send the rest of the staking rewards to the new SimpleRewarders, and ask users to migrate their funds to the new SimpleRewarders</li>\n</ul>\n<h3 id=\"the-issue\" style=\"position:relative;\"><a href=\"#the-issue\" aria-label=\"the issue permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The Issue</h3>\n<p>We initially got a report from multiple users in discord that they were having trouble swapping tokens in the new DAI/USDC/USDT pools. We initially chalked these up to RPC issues, which are not abnormal with the current Evmos infrastructure. However, when we got the following report from GV of Swiss Staking, we knew something was probably wrong as a power user like GV would know to change the RPCs and to try the transaction again after waiting a few minutes.</p>\n<p><img src=\"./../../../public/static/images/kinesis/gv_swiss_staking.png\" alt=\"GV&#x27;s message\"></p>\n<p>We then tested the exact swap ourselves, axlDAI for ibc G-DAI and got the following error.</p>\n<p><img src=\"./../../../public/static/images/kinesis/swap-error-message.png\" alt=\"Swap Error Message\"></p>\n<p>The error is <code class=\"language-text\">execution reverted: SafeMath: division by zero</code>. This was a very bad sign and we immediately knew something went very badly wrong with the contracts - this error could no longer be from Evmos infrastructure or our frontend.</p>\n<p>After triaging this a bit further, we found the exact line in the frontend that made this call to the <code class=\"language-text\">swap()</code> function and thus we knew the issue was in the <code class=\"language-text\">swap()</code> function of the contracts.</p>\n<p>The swap function is very simple, just two lines:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">    function swap(\n        uint8 tokenIndexFrom,\n        uint8 tokenIndexTo,\n        uint256 dx,\n        uint256 minDy,\n        uint256 deadline\n    )\n        external\n        payable\n        virtual\n        nonReentrant\n        whenNotPaused\n        deadlineCheck(deadline)\n        returns (uint256)\n    \\{\n        updateSwapFee(minDy);\n        return swapStorage.swap(tokenIndexFrom, tokenIndexTo, dx, minDy);\n    \\}</code></pre></div>\n<p>There were only three functions that could have held this error, the <code class=\"language-text\">deadlineCheck()</code> modifier, the <code class=\"language-text\">updateSwapFee()</code> function, or the <code class=\"language-text\">swapStorage.swap()</code> function.</p>\n<p>The <code class=\"language-text\">deadlineCheck()</code> does not have a SafeMath div call, meaning the error could not have been there. Additionally, the SafeMath divs in <code class=\"language-text\">swapStorage.swap()</code> both use constants, meaning it could not be those functions either.</p>\n<p>This narrows down the scope of the issue to the <code class=\"language-text\">updateSwapFee()</code> function. For full disclosure, this is a newly added function, which dynamically changes the swap fee based on the volume swapped in the last hour. There is one SafeMath div in the function.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">uint256 newSwapFee =\n(feeFactor.mul(hourlyVolume))\n.div(averageVolume).add(baseFee);</code></pre></div>\n<p>Since the error was <code class=\"language-text\">execution reverted: SafeMath: division by zero</code>, we know that averageVolume is zero, making the entire <code class=\"language-text\">swap()</code> function revert.</p>\n<p>Average Volume is set in <a href=\"https://github.com/kinesis-labs/contract/blob/69c3449b1026bc217f12f7ac5f1f7ee14656ec0a/contracts/Swap.sol#L527\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Line 527</a> and we can quickly see the error.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">averageVolume = averageVolume\n.add(averageVolume.sub(recentVolume[recentVolumePointer].volume))\n.mul((10 minutes / (block.timestamp.sub(creationTimestamp))));</code></pre></div>\n<p>10 minutes after the contract is created, <code class=\"language-text\">block.timestamp.sub(creationTimestamp)</code> will be greater than <code class=\"language-text\">10 minutes</code>. In solidity, there are no decimals, so <code class=\"language-text\">1/2</code> will round to zero. Thus, <code class=\"language-text\">(10 minutes/block.timestamp.sub(creationTimestamp))</code> will round to zero after the first 10 minutes, meaning that Average Volume will always be set to zero, and thus the <code class=\"language-text\">swap()</code> function will always revert.</p>\n<h2 id=\"triaging-the-issue\" style=\"position:relative;\"><a href=\"#triaging-the-issue\" aria-label=\"triaging the issue permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Triaging the Issue</h2>\n<p>The first thing we tested was withdrawing liquidity as we want to be 100% sure that user funds were not at risk. To this effect, we tested Deposit, Withdraw, Stake All, and Unstake All to ensure there are no issues with any of these functionality. Luckily, there were no issues here and USER FUNDS ARE SAFE.</p>\n<p>Thus, there are a few issues to keep in mind. We want Kinesis to be a useful application on Evmos instead of a place to stake stablecoins to receive Evmos. Thus, we would need to either upgrade the contract or to deploy new contracts. Unfortunately, we do not have upgrade functionality to violate the core principles of decentralization and immutability.</p>\n<p>We then decided the best way forward would be to redeploy the contracts and send the rest of the rewards(87,343.52 wEVMOS) to the new simple rewarders.</p>\n<h2 id=\"next-steps\" style=\"position:relative;\"><a href=\"#next-steps\" aria-label=\"next steps permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Next Steps</h2>\n<h3 id=\"redeploying-contracts\" style=\"position:relative;\"><a href=\"#redeploying-contracts\" aria-label=\"redeploying contracts permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Redeploying Contracts</h3>\n<p>Contracts have already been redeployed to these addresses:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">USDCPool: 0x35bF604084FBE407996c394D3558E58c90281000\nUSDCPoolLPToken: 0xfD2fd675176a8Ed1CF643886ee557929FDEcBBfD\nUSDTPool: 0x89E9703309DA4aC51C739D7d674F91489830310E\nUSDTPoolLPToken: 0x78549EF94dB08E8bf2e528F0aE97F186Fc51185E\nDAIPool: 0x155377C4f5489026cD8340fF350ae6aa082FBE69\nDAIPoolLPToken: 0x9607FFD92DC6846F913129d8351a848240BEC4E9\nMiniChefV2: 0x82a4A94eC86f03bf1a5979B41e2Ae8478C93f9C3\nSimpleRewarder_usdc: 0x5Ff26712e2Ec975d7F25a31Ba7c9FbA395655076\nSimpleRewarder_usdt: 0xDc85D4f941B8adc7cF223C9AFd5305c2684D7090\nSimpleRewarder_dai: 0x0BCC8b2E95e0e59a23fc6D8DD653636047cf3C3f</code></pre></div>\n<p>The frontend has also been redeployed here with the new pools and the old pools.\n<a href=\"https://frontend-git-a-featpostmortem-kinesislabs.vercel.app/#/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://frontend-git-a-featpostmortem-kinesislabs.vercel.app/#/</a></p>\n<h3 id=\"sending-rewards\" style=\"position:relative;\"><a href=\"#sending-rewards\" aria-label=\"sending rewards permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Sending Rewards</h3>\n<p>Afterwards, we need sent the rest of the wEVMOS liquidity mining rewards to the new Simple Rewarders. This process would be the exact same as what LPX already has done in the past.</p>\n<ul>\n<li>LPX, can you initiate the transactions for this when we have agreed to a path forward?</li>\n</ul>\n<h3 id=\"public-announcement-to-migrate-funds\" style=\"position:relative;\"><a href=\"#public-announcement-to-migrate-funds\" aria-label=\"public announcement to migrate funds permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Public Announcement to Migrate Funds</h3>\n<p>We will need to make a public announcement for users to migrate funds from the old pools which will be depreceated to the new pools. There are two options here:</p>\n<ul>\n<li>We can admit we messed up and explain the full scope of the issue</li>\n<li>We can hide this as a routine upgrade to activate dynamic fees.</li>\n</ul>\n<p>We are leaning towards the former reason, however, if others in the Evmos ecosystem wish to use the latter reasoning to keep confidence in the ecosystem, that is understandable as well.</p>\n<h3 id=\"set-rewards-per-second-to-zero\" style=\"position:relative;\"><a href=\"#set-rewards-per-second-to-zero\" aria-label=\"set rewards per second to zero permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Set Rewards Per Second to Zero</h3>\n<p>Finally, we would set rewards per second on the current SimpleRewarder Contracts. This is to ensure everyone can unstake their LP tokens from Minichef such that they can withdraw their LP tokens, as if there are not enough LP tokens on Minichef, users will no longer be able to unstake their LP tokens.</p>\n<ul>\n<li>LPX, can you initiate these three transactions? You would need to call</li>\n<li>-</li>\n</ul>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">function setRewardPerSecond(uint256 _rewardPerSecond) public onlyOwner \\{\n        rewardPerSecond = _rewardPerSecond;\n        emit LogRewardPerSecond(_rewardPerSecond);\n    \\}</code></pre></div>\n<p>in SimpleRewarder.sol with <code class=\"language-text\">_rewardPerSecond</code> set to 0.</p>\n<h3 id=\"todos-on-kinesis-end\" style=\"position:relative;\"><a href=\"#todos-on-kinesis-end\" aria-label=\"todos on kinesis end permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>TODOs on Kinesis’ End</h3>\n<p>Update Minichef to the new minichef contract address on the frontend when rewards are live on the new contracts</p>","fields":{"slug":"/posts/kinesis-labs/dynamic-fees-postmortem","tagSlugs":["/tag/kinesis-labs/","/tag/de-fi/","/tag/reflections/"]},"frontmatter":{"date":"2023-01-20T00:00:00","description":"A postmortem of the security exploit in the Kinesis Contracts.","tags":["Kinesis Labs","DeFi","Reflections"],"title":"Dynamic Fees Postmortem"}}},"pageContext":{"slug":"/posts/kinesis-labs/dynamic-fees-postmortem"}},"staticQueryHashes":["251939775","401334301","825871152"]}