Staking Pool Fixes and NXM Batch Withdrawal Changes Smart Contract Audit

<html><head><style>

@import url(https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap);@import url('https://fonts.googleapis.com/css2?family=Source Code Pro:wght@400;500;600;700&display=swap');ul{opacity:1;}table{border:none !important;}a,code,pre,tt{overflow-wrap:break-word;word-wrap:break-word}.chroma .lntable,.chroma .lntd{margin:0;border:0;padding:0}pre,table{width:100%}.findings-count td,.findings-count th,.rating-critical,.rating-high,.rating-informational,.rating-low,.rating-medium,.status-closed,.status-open,.status-resolved{text-align:center}.rating-critical,.rating-high,.rating-informational,.rating-low,.rating-medium,.status-closed,.status-open,.status-resolved,a,h1,h2,h3{font-weight:500}:root{--color-link:#f66263;--color-heading:#f66263;--color-codeblock:#f5f2f0;--color-codeblock-border:#f5f2f0;--color-critical:#cc7ab9;--color-high:#ff6263;--color-medium:#f68463;--color-low:#f6b263;--color-informational:#6ba2f6;--color-open:#f6d263;--color-closed:#b6b6b6;--color-resolved:#9fbf9f;--size-200:0.694rem;--size-300:0.833rem;--size-400:1rem;--size-500:1.2rem;--size-600:1.44rem;--size-700:1.728rem;--size-800:2.074rem;--size-900:2.488rem}.report-container{color:#222;font-family:Montserrat,sans-serif;line-height:1.6;margin:1rem;font-size:var(--size-400)}a{color:var(--color-link)}@media screen and (min-width:1600px){.report-main{display:grid;grid-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) !important}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>

</head><body><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">Staking pool fixes and NXM batch withdrawals</div>

<div class="frontpage-subtitle">Nexus Mutual, 21 August 2024</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-nxm-spb-001-censoring-assessors-by-front-running">IO-NXM-SPB-001 Censoring assessors by front-running</a></li>

<li>

<a href="#io-nxm-spb-002-validate-ipfshash">IO-NXM-SPB-002 Validate ipfsHash</a></li>

<li>

<a href="#io-nxm-spb-003-overwriting-of-ipfshash">IO-NXM-SPB-003 Overwriting of ipfsHash</a></li>

</ul>

</li>

<li>

<a href="#code-quality-improvement-suggestions">Code quality improvement suggestions</a></li>

<li>

<a href="#specification">Specification</a><ul>

<li>

<a href="#staking-pool-rewards">Staking pool rewards</a></li>

<li>

<a href="#batch-nxm-withdrawals">Batch NXM withdrawals</a></li>

<li>

<a href="#skip-deallocations-when-burning-in-the-grace-period">Skip deallocations when burning in the grace period</a></li>

<li>

<a href="#staking-pool-metadata">Staking pool metadata</a></li>

</ul>

</li>

</ul>

</nav>

<article class="report">

<h1 id="introduction">Introduction</h1>

<p>iosiro was commissioned by Nexus Mutual to perform a smart contract audit of several feature enhancements and bug fixes. The audit was conducted between 12 August 2024 and 19 August 2024 by one auditor for a total of five days.</p>

<h4 id="overview">Overview</h4>

<p>During the audit, a medium-risk issue was identified and subsequently remediated - this issue would have allowed malicious actors to censor assessors. Several code quality and design suggestions were also provided. A breakdown of the issues reported and their status at the conclusion of the audit is provided below:</p>

<table class="findings-count">

<thead>

<tr>

<th> </th>

<th>Critical</th>

<th>High</th>

<th>Medium</th>

<th>Low</th>

<th>Informational</th>

</tr>

</thead>

<tbody>

<tr>

<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>0</td>

<td>1</td>

<td>1</td>

<td>1</td>

</tr>

<tr>

<td>Closed</td>

<td>0</td>

<td>0</td>

<td>0</td>

<td>0</td>

<td>0</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 issues or security vulnerabilities.</p>

<ul>

<li><strong>Project name:</strong> Nexus Mutual</li>

<li><strong>Initial audit commit:</strong> <a href="https://github.com/NexusMutual/smart-contracts/tree/7087907b5b6d8bfb83a12755e8f2ce82f56fcb4f">7087907</a></li>

<li><strong>Final review commit:</strong> <a href="https://github.com/NexusMutual/smart-contracts/tree/e62eed05b4e159b105701488458e9e40b94440e9">e62eed0</a></li>

<li><strong>Files:</strong>  <code>contracts/modules/assessment/Assessment.sol</code>, <code>contracts/modules/capital/SwapOperator.sol</code>, <code>contracts/modules/cover/Cover.sol</code>, <code>contracts/modules/staking/StakingExtrasLib.sol</code>, <code>contracts/modules/staking/StakingPool.sol</code>, <code>contracts/modules/staking/StakingProducts.sol</code>, <code>contracts/modules/token/TokenController.sol</code></li>

</ul>

<p>A specification is available in the <a href="#specification">Specification section</a> of this report.</p>

<h1 id="disclaimer">Disclaimer</h1>

<p>This report aims to provide an overview of the assessed smart contracts' risk exposure and a guide to improving their security posture by addressing identified issues. The audit, limited to specific source code at the time of review, sought to:</p>

<ul>

<li>Identify potential security flaws.</li>

<li>Verify that the smart contracts' functionality aligns with their documentation.</li>

</ul>

<p>Off-chain components, such as backend web application code, keeper functionality, and deployment scripts were explicitly not in-scope of this audit.</p>

<p>Given the unregulated nature and ease of cryptocurrency transfers, operations involving these assets face a high risk from cyber attacks. Maintaining the highest security level is crucial, necessitating a proactive and adaptive approach 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-NXM-SPB-001">IO-NXM-SPB-001</a></td>

<td>Censoring assessors by front-running</td>

<td class="rating-medium">Medium</td>

<td class="status-resolved">Resolved</td>

</tr>

<tr>

<td><a href="#IO-NXM-SPB-002">IO-NXM-SPB-002</a></td>

<td>Validate ipfsHash</td>

<td class="rating-low">Low</td>

<td class="status-resolved">Resolved</td>

</tr>

<tr>

<td><a href="#IO-NXM-SPB-003">IO-NXM-SPB-003</a></td>

<td>Overwriting of ipfsHash</td>

<td class="rating-informational">Informational</td>

<td class="status-resolved">Resolved</td>

</tr>

</tbody>

</table>

<p>Each issue identified during the audit has been assigned a risk rating. The rating is determined based on the criteria outlined below.</p>

<dl>

<dt>Critical risk</dt>

<dd>The issue could result in the theft of funds from the contract or its users.</dd>

<dt>High risk</dt>

<dd>The issue could result in the loss of funds for the contract owner or its users.</dd>

<dt>Medium risk</dt>

<dd>The issue resulted in the code being dysfunctional or the specification being implemented incorrectly.</dd>

<dt>Low risk</dt>

<dd>A design or best practice issue that could affect the ordinary functioning of the contract.</dd>

<dt>Informational</dt>

<dd>An improvement related to best practice or a suboptimal design pattern.</dd>

</dl>

<p>In addition to a risk rating, each issue is assigned a status:</p>

<dl>

<dt>Open</dt>

<dd>The issue remained present in the code as of the final commit reviewed and may still pose a risk.</dd>

<dt>Resolved</dt>

<dd>The issue was identified during the audit and has since been satisfactorily addressed, removing the risk it posed.</dd>

<dt>Closed</dt>

<dd>The issue was identified during the audit and acknowledged by the developers as an acceptable risk without actioning any change.</dd>

</dl>

<a name="IO-NXM-SPB-001"></a><h2 id="io-nxm-spb-001-censoring-assessors-by-front-running" class="break-before"><strong>IO-NXM-SPB-001</strong> Censoring assessors by front-running</h2>

<table class="metadata">

<tbody>

<tr>

<td class="rating-medium">Medium</td>

<td class="status-open">Open</td>

<td><a href="https://github.com/NexusMutual/smart-contracts/blob/eef8c8672b8bebb963a60860fb9ab61d42515193/contracts/modules/assessment/Assessment.sol#L156">Assessment.sol#L156</a></td>

</tr>

</tbody>

</table>

<p>It is possible to front-run calls to <code>Assessment::castVotes(...)</code> with <code>Assessment::unstakeAllFor(...)</code>, preventing assessors from casting votes. An attacker could leverage this to censor specific accounts that might vote against or for a particular claim.</p>

<p>The following unit test was added to illustrate the issue:</p>

<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="gh">diff --git a/test/unit/Assessment/unstakeAllFor.js b/test/unit/Assessment/unstakeAllFor.js

</span></span></span><span class="line"><span class="cl"><span class="gh">index b22027d4..08e2719d 100644

</span></span></span><span class="line"><span class="cl"><span class="gh"></span><span class="gd">--- a/test/unit/Assessment/unstakeAllFor.js

</span></span></span><span class="line"><span class="cl"><span class="gd"></span><span class="gi">+++ b/test/unit/Assessment/unstakeAllFor.js

</span></span></span><span class="line"><span class="cl"><span class="gi"></span><span class="gu">@@ -65,6 +65,22 @@ describe('unstakeAllFor', function () {

</span></span></span><span class="line"><span class="cl"><span class="gu"></span>await expect(unstakeForAtExpiry).to.be.revertedWithCustomError(assessment, 'StakeLockedForAssessment');

</span></span><span class="line"><span class="cl">});

</span></span><span class="line"><span class="cl">

</span></span><span class="line"><span class="cl"><span class="gi">+  it('should not be possible to front-run user votes', async function () {

</span></span></span><span class="line"><span class="cl"><span class="gi">+    const fixture = await loadFixture(setup);

</span></span></span><span class="line"><span class="cl"><span class="gi">+    const { assessment, individualClaims } = fixture.contracts;

</span></span></span><span class="line"><span class="cl"><span class="gi">+    const [user, otherUser] = fixture.accounts.members;

</span></span></span><span class="line"><span class="cl"><span class="gi">+    const amount = parseEther('100');

</span></span></span><span class="line"><span class="cl"><span class="gi">+

</span></span></span><span class="line"><span class="cl"><span class="gi">+    await assessment.connect(user).stake(amount);

</span></span></span><span class="line"><span class="cl"><span class="gi">+

</span></span></span><span class="line"><span class="cl"><span class="gi">+    await assessment.connect(otherUser).unstakeAllFor(user.address);

</span></span></span><span class="line"><span class="cl"><span class="gi">+

</span></span></span><span class="line"><span class="cl"><span class="gi">+    await individualClaims.submitClaim(0, 0, amount, '');

</span></span></span><span class="line"><span class="cl"><span class="gi">+

</span></span></span><span class="line"><span class="cl"><span class="gi">+    const { timestamp } = await ethers.provider.getBlock('latest'); // store the block.timestamp on time of vote

</span></span></span><span class="line"><span class="cl"><span class="gi">+    await assessment.connect(user).castVotes([0], [true], ['Assessment data hash'], 0);

</span></span></span><span class="line"><span class="cl"><span class="gi">+  });

</span></span></span><span class="line"><span class="cl"><span class="gi">+

</span></span></span><span class="line"><span class="cl"><span class="gi"></span>it('reverts if system is paused', async function () {

</span></span><span class="line"><span class="cl">const fixture = await loadFixture(setup);

</span></span><span class="line"><span class="cl">const { assessment, master } = fixture.contracts;

</span></span></code></pre><h3 id="recommendation">Recommendation</h3>

<p>Removing <code>unstakeAllFor</code>, allowing it to be only called from the <code>TokenController</code>, or implementing a minimum lock period for assessment staking would prevent this attack.</p>

<p>Adding <code>multicall</code> support to <code>Assessment</code> would allow users to call <code>stake</code> and <code>castVotes</code> atomically. Leveraging this <code>multicall</code> pattern on the front end would partially mitigate the risk.</p>

<h3 id="update">Update</h3>

<p>Fixed in commit <a href="https://github.com/NexusMutual/smart-contracts/commit/33485ecbbfcca572d07a4fd1cdcdae4753199662">33485ec</a>. The <code>onlyTokenController</code> access modifier was added to the <code>Assessment::unstakeAllFor(...)</code> function.</p>

<a name="IO-NXM-SPB-002"></a><h2 id="io-nxm-spb-002-validate-ipfshash" class="break-before"><strong>IO-NXM-SPB-002</strong> Validate ipfsHash</h2>

<table class="metadata">

<tbody>

<tr>

<td class="rating-low">Low</td>

<td class="status-open">Open</td>

<td><a href="https://github.com/NexusMutual/smart-contracts/blob/eef8c8672b8bebb963a60860fb9ab61d42515193/contracts/modules/staking/StakingProducts.sol#L664">StakingProducts.sol#L664</a></td>

</tr>

</tbody>

</table>

<p>For <code>CoverProducts</code>, the <code>ipfsHash</code> is validated to be not empty. However, with <code>StakingProducts</code>, it is possible to set an empty <code>ipfsHash</code>.</p>

<p>Furthermore, setting an empty <code>ipfsHash</code> could bypass the intended once-off execution of the <code>StakingProducts::setInitialMetadata(...)</code> function. If the manager of the first pool were to reset the <code>ipfsHash</code> of their pool, the Advisory Board would be able to reinvoke the function and change the metadata of all staking pools as described in the "Overwriting of ipfsHash" issue included in this report.</p>

<h3 id="recommendation-1">Recommendation</h3>

<p>The <code>ipfsHash</code> should be validated to be not empty, as it is required for all staking pools.</p>

<h3 id="update-1">Update</h3>

<p>Fixed in commit <a href="https://github.com/NexusMutual/smart-contracts/commit/7a120f50886043b00c537edf0761f1f8a0ad9be1">7a120f5</a>.</p>

<a name="IO-NXM-SPB-003"></a><h2 id="io-nxm-spb-003-overwriting-of-ipfshash" class="break-before"><strong>IO-NXM-SPB-003</strong> Overwriting of ipfsHash</h2>

<table class="metadata">

<tbody>

<tr>

<td class="rating-informational">Informational</td>

<td class="status-open">Open</td>

<td><a href="https://github.com/NexusMutual/smart-contracts/blob/eef8c8672b8bebb963a60860fb9ab61d42515193/contracts/modules/staking/StakingProducts.sol#L673">StakingProducts.sol#L673</a></td>

</tr>

</tbody>

</table>

<p>When calling <code>StakingProducts::setInitialMetadata(...)</code>, the advisory board can overwrite the <code>ipfsHash</code> set by a staking pool owner.</p>

<h3 id="recommendation-2">Recommendation</h3>

<p>Although it is assumed that <code>StakingProducts::setInitialMetadata(...)</code> will be called during the upgrade, it would be safer to skip any pools that already have an <code>ipfsHash</code> set.</p>

<h3 id="update-2">Update</h3>

<p>Fixed in commit <a href="https://github.com/NexusMutual/smart-contracts/commit/0692f7288b1884af3725d1c2799745edaaf81264">0692f72</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>1</td>

<td><a href="https://github.com/NexusMutual/smart-contracts/blob/eef8c8672b8bebb963a60860fb9ab61d42515193/contracts/modules/staking/StakingProducts.sol#L676">StakingProducts.sol#L676</a></td>

<td>In <code>StakingProducts</code>, all other reverts use custom errors; however, the new <code>setInitialMetadata</code> function uses <code>require</code> strings. For consistency and to reduce contract size, custom errors for <code>"StakingProducts: Metadata length mismatch"</code> and <code>"StakingProducts: Metadata already set"</code> should be introduced.</td>

</tr>

<tr>

<td>2</td>

<td><a href="https://github.com/NexusMutual/smart-contracts/blob/eef8c8672b8bebb963a60860fb9ab61d42515193/contracts/modules/cover/Cover.sol#L699">Cover.sol#L699</a></td>

<td>In <code>Cover::updateStakingPoolsRewardShares(...)</code> the length of the <code>tokensId</code> array is not validated to equal to <code>stakingPoolCount</code>, which can cause an index out-of-bounds revert. Similarly, each item in the <code>tokensIds</code> array is not validated to contain <code>MAX_ACTIVE_TRANCHES</code> elements. e.g. <code>require(tokenIds[poolIndex].length == MAX_ACTIVE_TRANCHES, "Invalid number of tranches")</code>.</td>

</tr>

<tr>

<td>3</td>

<td><a href="https://github.com/NexusMutual/smart-contracts/blob/eef8c8672b8bebb963a60860fb9ab61d42515193/contracts/modules/cover/Cover.sol#L705">Cover.sol#L705</a></td>

<td>Declare <code>MAX_ACTIVE_TRANCHES</code> as constant in <code>Cover.sol</code> and update the <code>for</code> loop to <code>for (uint trancheIdx = 0; trancheIdx < MAX_ACTIVE_TRANCHES; trancheIdx++)</code> instead of using the hardcoded value, e.g. <code>trancheIdx <= 7</code></td>

</tr>

<tr>

<td>4</td>

<td><a href="https://github.com/NexusMutual/smart-contracts/blob/eef8c8672b8bebb963a60860fb9ab61d42515193/contracts/modules/staking/StakingPool.sol#L528">StakingPool.sol#L528</a></td>

<td>In <code>StakingPool::withdraw(...)</code> an event is emitted using the <code>msg.sender</code>; however, since the function is permissionless it cannot be assumed that the <code>msg.sender</code> is the owner of the NFT. This is clear from the use of the function from within <code>TokenController::withdrawNXM(...)</code>. The result of <code>stakingNFT.ownerOf(tokenId)</code> should be cached and emitted as the <code>user</code> parameter for the <code>Withdraw</code> event instead of <code>msg.sender</code>.</td>

</tr>

</tbody>

</table>

<h4 id="update-3">Update</h4>

<ol>

<li>Won't fix. The function will be deprecated and removed shortly after upgrade.</li>

<li>Won't fix. The function will be deprecated and removed shortly after upgrade.</li>

<li>Fixed in commit <a href="https://github.com/NexusMutual/smart-contracts/commit/d299b000fdd67cc6ee9b6867a33760c635daccd6">d299b00</a>.</li>

<li>Fixed in commit <a href="https://github.com/NexusMutual/smart-contracts/blob/eef8c8672b8bebb963a60860fb9ab61d42515193/contracts/modules/staking/StakingPool.sol#L528">eef8c86</a>.</li>

</ol>

<h1 id="specification">Specification</h1>

<p>The following section outlines the functional changes to the system at a high level as implemented in the codebase. The auditing team should be informed of any perceived points of conflict with the design specification to determine the source of the discrepancy.</p>

<h2 id="staking-pool-rewards">Staking pool rewards</h2>

<p>The calculation of the rewards share for staking pools was reworked to correct an error that resulted in the manager receiving fewer shares than intended. The new implementation not only corrects the error but also dramatically simplifies the calculation by removing the tranche bonus.</p>

<p>To correct the reward shares for existing deposits, temporary functions were added that recalculate the reward shares for each deposit and tranche. All the temporary functions implemented access control and could only be invoked by the Advisory Council. During the audit, the idempotency of the functions was verified, even though it was expected that they would only be invoked during the upgrade.</p>

<p>The logic for updating the pool fee was also reworked to ensure that the revised formula is correctly applied to the total and manager rewards shares.</p>

<h2 id="batch-nxm-withdrawals">Batch NXM withdrawals</h2>

<p>Additional functions were added to simplify withdrawing NXM from the system. The new withdrawNXM function leveraged mostly existing functionality since most existing functions were already permissionless. However, an access-controlled function was introduced to withdraw the NXM staked for assessments.</p>

<h2 id="skip-deallocations-when-burning-in-the-grace-period">Skip deallocations when burning in the grace period</h2>

<p>A flaw in the <code>burnStake</code> function was identified that would cause capacity that had already been deallocated to be deallocated again when paying out a claim during the grace period. A fix was introduced to ensure that capacity is not deallocated if the cover has already expired.</p>

<h2 id="staking-pool-metadata">Staking pool metadata</h2>

<p>Similar to a previous change to <code>CoverProducts,</code> the <code>ipfsHash</code> for staking pools was changed so that it is committed to storage instead of emitted as an event parameter. This simplifies retrieving a staking pool's metadata, as event indexing is a resource-intensive process.</p>

<p>Functions were added to the <code>StakingProducts</code> smart contract that allows pool managers to set the ipfsHash for their pool. A temporary function that can only be called by the Advisory Board was added to enable the setting of each pool's metadata during the upgrade process.</p>

</article>

</main>

</div>

</body></html>

Secure your system.
Request a service
Start Now