Thales Sports AMM v2 Smart Contract Audit

<html><head>
<style>@import url(https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&amp;display=swap);@import url('https://fonts.googleapis.com/css2?family=Source Code Pro:wght@400;500;600;700&amp;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:25em}.frontpage-logo,.frontpage-subtitle,.frontpage-title,.report{grid-column:2}}@media screen and (max-width:1599px){ .report-main{ margin: 0 auto;max-width:70em} }h1,h2,h3,h4,h5,h6{margin-bottom:0}.toc>ul>li>a,dt,h4,h5,h6,table th{font-weight:600}h1,h2{color:var(--color-heading)}h1{font-size:var(--size-800)}h2{font-size:var(--size-700)}h3{font-size:var(--size-600)}h4{font-size:var(--size-500)}h1 strong,h2 strong,h3 strong,h4 strong{font-weight:400;font-size:.7em;font-family:"Source Code Pro",monospace}h2 strong::after{content:"\a";white-space:pre}code,pre,tt{font-family:"Source Code Pro",monospace,sans-serif;background-color:var(--color-codeblock);white-space:pre-wrap}code,tt{padding:1px 3px;border-radius:2px}pre{box-sizing:border-box;padding:10px;overflow:auto;word-break:break-all}pre code,tt{font-size:inherit;background:0 0;border:none;padding:0}.findings code,h2 code{background-color:inherit;border-width:0}@media screen and (min-width:600px){dl{display:grid;grid-gap:0.5em}dt{grid-column:1}dd{grid-column:2}}@media screen and (max-width:599px){dl{display:block}}table{background-color:inherit;max-width:100%;min-width:100%;border:none}table thead th{border-bottom:2px solid #222}table td,table th{text-align:left;border:none}table,td,th{border-collapse:collapse}.findings-count thead tr th:first-of-type{border-style:none}.findings-count tbody td:first-child{text-align:right;width:3em;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)}#document-control table,.findings td,.metadata td{font-size:.9em}.findings td:first-of-type{white-space:nowrap;word-break:keep-all;font-family:"Source Code Pro",monospace;vertical-align:top}.findings td:nth-of-type(2){vertical-align:bottom}@media screen{.audit-header,.report{max-width:65rem}h1{margin-top:3em}h2{margin-top:2em}table th{padding:6px}table td{padding:8px 6px}.findings td:nth-of-type(2){min-width:2em}}.metadata td:last-of-type{background-color:#eee}.metadata td:first-of-type,.metadata td:nth-of-type(2){width:8em}.toc ul{list-style:none;margin-left:0;padding-left:0}.toc li ul{margin-left:3em}.toc{counter-reset:tocSectionCounter}.toc>ul>li::before{content:counter(tocSectionCounter) ". ";font-weight:600;padding-right:4px}.toc>ul>li{counter-increment:tocSectionCounter}.report{counter-reset:sectionCounter}.report h1::before{content:counter(sectionCounter) ". ";font-weight:400;padding-right:6px}.report h1{counter-increment:sectionCounter}hr{display:none}@media print{.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}.metadata td{padding:.3em}.codequality{font-size:.95em}td{padding:4px}}.page-header{margin-top:-1cm;opacity:.8;position:running(pageHeaderRunning)}.page-header svg{width:34mm}@media screen{.page-header,.frontpage-logo,.frontpage-title,.frontpage-subtitle{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>
 <main class="report-main">
     <nav class="toc">
       <h1>Contents</h1>
       <ul>
<li>
<a href="#introduction">Introduction</a></li>
<li>
<a href="#disclaimer">Disclaimer</a></li>
<li>
<a href="#methodology">Methodology</a></li>
<li>
<a href="#audit-findings">Audit findings</a><ul>
<li>
<a href="#io-thl-sv2-001-round-closing-processor-will-receive-staking-volume-updates-for-processed-depositors">IO-THL-SV2-001 Round closing processor will receive staking volume updates for processed depositors</a></li>
<li>
<a href="#io-thl-sv2-026-pausable-callback-could-prevent-ticket-exercise">IO-THL-SV2-026 Pausable callback could prevent ticket exercise</a></li>
<li>
<a href="#io-thl-sv2-027-freebetsholder-does-not-account-for-cancelled-tickets">IO-THL-SV2-027 FreeBetsHolder does not account for cancelled tickets</a></li>
<li>
<a href="#io-thl-sv2-002-staking-volume-not-updated-for-depositors-making-partial-withdrawals">IO-THL-SV2-002 Staking volume not updated for depositors making partial withdrawals</a></li>
<li>
<a href="#io-thl-sv2-003-ether-send-method-used-instead-of-call">IO-THL-SV2-003 Ether send method used instead of call</a></li>
<li>
<a href="#io-thl-sv2-005-possible-revert-due-to-insufficient-oracle-payment-token-balance">IO-THL-SV2-005 Possible revert due to insufficient oracle payment token balance</a></li>
<li>
<a href="#io-thl-sv2-006-changeable-merkle-roots">IO-THL-SV2-006 Changeable Merkle roots</a></li>
<li>
<a href="#io-thl-sv2-007-no-maximum-for-safeboximpact">IO-THL-SV2-007 No maximum for safeBoxImpact</a></li>
<li>
<a href="#io-thl-sv2-008-no-maximum-for-safeboxfee">IO-THL-SV2-008 No maximum for safeBoxFee</a></li>
<li>
<a href="#io-thl-sv2-009-cumulative-profit-and-loss-calculated-incorrectly-for-rounds-with-0-allocation">IO-THL-SV2-009 Cumulative profit and loss calculated incorrectly for rounds with 0 allocation</a></li>
<li>
<a href="#io-thl-sv2-011-unused-withdraw-function">IO-THL-SV2-011 Unused withdraw function</a></li>
<li>
<a href="#io-thl-sv2-012-buy-in-amount-not-set-to-exact-amount-received-after-on-ramp">IO-THL-SV2-012 Buy-in amount not set to exact amount received after on-ramp</a></li>
<li>
<a href="#io-thl-sv2-013-broad-access-control">IO-THL-SV2-013 Broad access control</a></li>
<li>
<a href="#io-thl-sv2-028-utilization-rate-not-accounted-for-when-funding-tickets-for-non-current-rounds">IO-THL-SV2-028 Utilization rate not accounted for when funding tickets for non-current rounds.</a></li>
<li>
<a href="#io-thl-sv2-029-users-can-have-less-than-minimum-liquidity-pool-deposit-on-subsequent-rounds">IO-THL-SV2-029 Users can have less than minimum liquidity pool deposit on subsequent rounds</a></li>
<li>
<a href="#io-thl-sv2-037-risk-multipliers-should-be-more-granular">IO-THL-SV2-037 Risk multipliers should be more granular</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="#amm">AMM</a></li>
<li>
<a href="#liquidity">Liquidity</a></li>
<li>
<a href="#results">Results</a></li>
<li>
<a href="#live-trading">Live Trading</a></li>
<li>
<a href="#free-bets">Free Bets</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 Thales to perform a smart contract audit of their Sports AMM v2 smart contracts. Two auditors conducted the audit between 8 May and 4 June 2024, using 20 audit days.</p>
<h4 id="overview">Overview</h4>
<p>The audit uncovered one high-risk, three medium-risk and twelve low-risk issues, summarized below:</p>
<table class="findings-count">
<thead>
<tr>
<th> </th>
<th>Critical</th>
<th>High</th>
<th>Medium</th>
<th>Low</th>
</tr>
</thead>
<tbody>
<tr>
<td>Open</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>Resolved</td>
<td>0</td>
<td>1</td>
<td>3</td>
<td>5</td>
</tr>
<tr>
<td>Closed</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>7</td>
</tr>
</tbody>
</table>
<p>Several code quality improvement suggestions were also made.</p>
<p>The high-risk issue could have allowed liquidity pool keepers to claim staking volume on behalf of liquidity pool depositors. The medium risk issues related to the incorrect allocation of staking volume for liquidity providers, edge cases that could lead to unallocated funds in FreeBetsHolder, and a potential temporary DoS of the liquidity rounds mechanism. The low-risk issues were related to a lack of limitations on administrative functionality, bugs in external view functions, and Solidity best practices.</p>
<h4 id="user-concerns">User concerns</h4>
<p>Users of the Sports AMM v2 protocol should note that Thales has implemented multiple mechanisms for fund recovery by administrators. Thales administrators are also authorized to cancel and set results for games in the same manner that Chainlink does – this functionality is in place to mitigate the risk of data provider errors leading to incorrectly resolved or perpetually unresolved games. Additionally, the majority of contracts in the system are upgradeable and thus subject to changes in functionality.</p>
<p>Liquidity providers should take note of the <code>safeBoxImpact</code> variable in <code>SportsAMMV2LiquidityPool</code>, which indicates the percentage of profit taken as a fee by Thales at the end of each liquidity round. This is configurable and can be set to a maximum of 100%.</p>
<hr/>
<h4 id="scope">Scope</h4>
<p>The assessment focused on the source files listed below, with all other files considered out of scope. Any out-of-scope code interacting with the assessed code was presumed to operate correctly without introducing functional or security vulnerabilities.</p>
<ul>
<li><strong>Project name:</strong> contracts-v2</li>
<li><strong>Initial audit commit:</strong> <a href="https://github.com/thales-markets/contracts-v2/tree/72fe18cb795ba3e47b437891b77e329462288ce8">72fe18c</a></li>
<li><strong>Final review commit:</strong> <a href="https://github.com/thales-markets/contracts-v2/tree/8541ad3c29a4d525b3214c77e1a0c652b3dfa647">8541ad3</a></li>
<li><strong>Files:</strong> SportsAMMV2Manager.sol, SportsAMMV2RiskManager.sol, SportsAMMV2.sol, TicketMastercopy.sol, Ticket.sol, FreeBetsHolder.sol, DefaultLiquidityProvider.sol, SportsAMMV2LiquidityPoolRoundMastercopy.sol, SportsAMMV2LiquidityPoolRound.sol, SportsAMMV2LiquidityPool.sol, LiveTradingProcessor.sol, ChainlinkResolver.sol, SportsAMMV2ResultManager.sol</li>
</ul>
<p>The audit scope also included Thales' MultiCollateralOnOffRamp contract, contained in a separate repository.</p>
<ul>
<li><strong>Project name:</strong> contracts</li>
<li><strong>Initial audit commit:</strong> <a href="https://github.com/thales-markets/contracts/blob/2d846ae81bdbe1e4773302f4a465b770aeb644d7/contracts/SpeedMarkets/MultiCollateralOnOffRamp.sol">2d846ae</a></li>
<li><strong>Final review commit:</strong> <a href="https://github.com/thales-markets/contracts/blob/45ee048f5723783570b6e584ebf074b8efd20fe9/contracts/SpeedMarkets/MultiCollateralOnOffRamp.sol">45ee048</a></li>
<li><strong>Files:</strong> MultiCollateralOnOffRamp.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-THL-SV2-001">IO-THL-SV2-001</a></td>
<td>Round closing processor will receive staking volume updates for processed depositors</td>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-026">IO-THL-SV2-026</a></td>
<td>Pausable callback could prevent ticket exercise</td>
<td class="rating-medium">Medium</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-027">IO-THL-SV2-027</a></td>
<td><code>FreeBetsHolder</code> does not account for cancelled tickets</td>
<td class="rating-medium">Medium</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-002">IO-THL-SV2-002</a></td>
<td>Staking volume not updated for depositors making partial withdrawals</td>
<td class="rating-medium">Medium</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-003">IO-THL-SV2-003</a></td>
<td>Ether <code>send</code> method used instead of <code>call</code></td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-005">IO-THL-SV2-005</a></td>
<td>Possible revert due to insufficient oracle payment token balance</td>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-006">IO-THL-SV2-006</a></td>
<td>Changeable Merkle roots</td>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-007">IO-THL-SV2-007</a></td>
<td>No maximum for <code>safeBoxImpact</code></td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-008">IO-THL-SV2-008</a></td>
<td>No maximum for <code>safeBoxFee</code></td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-009">IO-THL-SV2-009</a></td>
<td>Cumulative profit and loss calculated incorrectly for rounds with 0 allocation</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-011">IO-THL-SV2-011</a></td>
<td>Unused withdraw function</td>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-012">IO-THL-SV2-012</a></td>
<td>Buy-in amount not set to exact amount received after on-ramp</td>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-013">IO-THL-SV2-013</a></td>
<td>Broad access control</td>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-028">IO-THL-SV2-028</a></td>
<td>Utilization rate not accounted for when funding tickets for non-current rounds.</td>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-029">IO-THL-SV2-029</a></td>
<td>Users can have less than minimum liquidity pool deposit on subsequent rounds</td>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
</tr>
<tr>
<td><a href="#IO-THL-SV2-037">IO-THL-SV2-037</a></td>
<td>Risk multipliers should be more granular</td>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</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-THL-SV2-001"></a><h2 id="io-thl-sv2-001-round-closing-processor-will-receive-staking-volume-updates-for-processed-depositors" class="break-before"><strong>IO-THL-SV2-001</strong> Round closing processor will receive staking volume updates for processed depositors</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-high">High</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L307">SportsAMMV2LiquidityPool.sol#L307</a>, <a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L612">SportsAMMV2LiquidityPool.sol#L612</a></td>
</tr>
</tbody>
</table>
<p>The function <code>updateStakingVolume</code> is called for each processed liquidity pool user in <code>processRoundClosingBatch</code> but updates the staking volume for <code>msg.sender</code>. As a result, the caller of this function will have their staking volume updated repeatedly in accordance with the balances of every non-withdrawing depositor in the batch.</p>
<h3 id="recommendation">Recommendation</h3>
<p>To correctly update staking volume for depositors, <code>updateStakingVolume</code>should be altered to take an account address instead of using <code>msg.sender</code>.</p>
<h3 id="client-response">Client response</h3>
<p>Fixed in <a href="https://github.com/thales-markets/contracts-v2/blob/3035f4d4b01ce6a5c89a056a73021cfa5d54dd8a/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L623">3035f4d</a>.</p>
<a name="IO-THL-SV2-026"></a><h2 id="io-thl-sv2-026-pausable-callback-could-prevent-ticket-exercise" class="break-before"><strong>IO-THL-SV2-026</strong> Pausable callback could prevent ticket exercise</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-medium">Medium</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/72fe18cb795ba3e47b437891b77e329462288ce8/contracts/core/FreeBets/FreeBetsHolder.sol#L132">FreeBetsHolder.sol#L132</a></td>
</tr>
</tbody>
</table>
<p>The callback function <code>confirmTicketResolved</code> in <code>FreeBetsHolder</code> will revert if it is called while the contract is paused. This will cause exercising of tickets held by <code>FreeBetsHolder</code> to fail. As all losing tickets must be exercised before a round can be closed, this could prevent or delay round closing.</p>
<h3 id="recommendation-1">Recommendation</h3>
<p>To mitigate this risk, the <code>notPaused</code> modifier should be removed from <code>confirmTicketResolved</code>.</p>
<h3 id="client-response-1">Client response</h3>
<p>Fixed in <a href="https://github.com/thales-markets/contracts-v2/blob/a09cac9902992e0acf13615cf50c0bec39e3faca/contracts/core/FreeBets/FreeBetsHolder.sol#L132">a09cac9</a>.</p>
<a name="IO-THL-SV2-027"></a><h2 id="io-thl-sv2-027-freebetsholder-does-not-account-for-cancelled-tickets" class="break-before"><strong>IO-THL-SV2-027</strong> <code>FreeBetsHolder</code> does not account for cancelled tickets</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-medium">Medium</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/72fe18cb795ba3e47b437891b77e329462288ce8/contracts/core/FreeBets/FreeBetsHolder.sol#L139-L147">FreeBetsHolder.sol#L139-L147</a></td>
</tr>
</tbody>
</table>
<p><code>FreeBetsHolder.confirmTicketResolved(...)</code> does not explicitly handle cases where the final payout of a ticket is the same as the buy-in amount — for example, if all markets on the ticket were cancelled. This case is treated identically to a loss, even though funds are returned to <code>FreeBetsHolder</code> during ticket exercise (these funds would be unallocated).</p>
<h3 id="recommendation-2">Recommendation</h3>
<p>To avoid having funds that are unallocated in the contract, the payout amount could be readded to the user’s balance, as shown below:</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="kt">uint</span> <span class="n">_exercized</span> <span class="o">=</span> <span class="n">Ticket</span><span class="p">(</span><span class="n">_resolvedTicket</span><span class="p">).</span><span class="n">finalPayout</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">_exercized</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">IERC20</span> <span class="n">_collateral</span> <span class="o">=</span> <span class="n">Ticket</span><span class="p">(</span><span class="n">_resolvedTicket</span><span class="p">).</span><span class="n">collateral</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kt">uint</span> <span class="n">buyInAmount</span> <span class="o">=</span> <span class="n">Ticket</span><span class="p">(</span><span class="n">_resolvedTicket</span><span class="p">).</span><span class="n">buyInAmount</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="n">balancePerUserAndCollateral</span><span class="p">[</span><span class="n">_user</span><span class="p">][</span><span class="kt">address</span><span class="p">(</span><span class="n">_collateral</span><span class="p">)]</span> <span class="o">+=</span> <span class="n">buyInAmount</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">  <span class="kt">uint</span> <span class="n">_earned</span> <span class="o">=</span> <span class="n">_exercized</span> <span class="o">-</span> <span class="n">buyInAmount</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">_earned</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>              
</span></span><span class="line"><span class="cl">  <span class="n">_collateral</span><span class="p">.</span><span class="n">safeTransfer</span><span class="p">(</span><span class="n">_user</span><span class="p">,</span> <span class="n">_earned</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre><h3 id="client-response-2">Client response</h3>
<p>Fixed in <a href="https://github.com/thales-markets/contracts-v2/blob/a09cac9902992e0acf13615cf50c0bec39e3faca/contracts/core/FreeBets/FreeBetsHolder.sol#L143-L144">a09cac9</a>.</p>
<a name="IO-THL-SV2-002"></a><h2 id="io-thl-sv2-002-staking-volume-not-updated-for-depositors-making-partial-withdrawals" class="break-before"><strong>IO-THL-SV2-002</strong> Staking volume not updated for depositors making partial withdrawals</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-medium">Medium</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L316">SportsAMMV2LiquidityPool.sol#L316</a></td>
</tr>
</tbody>
</table>
<p>Liquidity pool users making partial withdrawals will not have their staking volume updated in accordance with the amount they are depositing to the next round, as the code path for partial withdrawals does not include a call to <code>updateStakingVolume</code>.</p>
<h3 id="recommendation-3">Recommendation</h3>
<p>A call to <code>updateStakingVolume</code> should be included in the partial withdrawal logic, reflecting the amount that is redeposited.</p>
<h3 id="client-response-3">Client response</h3>
<p>Fixed in <a href="https://github.com/thales-markets/contracts-v2/blob/3035f4d4b01ce6a5c89a056a73021cfa5d54dd8a/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L322">3035f4d</a>.</p>
<a name="IO-THL-SV2-003"></a><h2 id="io-thl-sv2-003-ether-send-method-used-instead-of-call" class="break-before"><strong>IO-THL-SV2-003</strong> Ether <code>send</code> method used instead of <code>call</code></h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/AMM/SportsAMMV2.sol#L624">SportsAMMV2.sol#L624</a></td>
</tr>
</tbody>
</table>
<p>In <code>_exerciseTicket</code>, if the user chooses to use ETH as their exercise collateral, they will be paid using <code>.send</code>. This could fail if the user’s address is a smart contract with a <code>receive</code> function that requires more than the fixed gas stipend of 2300.</p>
<h3 id="recommendation-4">Recommendation</h3>
<p>Instead of <code>.send</code>, the <code>.call</code> method should be used to ensure that enough gas is made available for the transfer. As the external functions that call <code>_exerciseTicket</code> use re-entrancy guards, this change should not pose a re-entrancy risk.</p>
<p>More information about this issue is available here: <a href="https://consensys.io/diligence/blog/2019/09/stop-using-soliditys-transfer-now/">https://consensys.io/diligence/blog/2019/09/stop-using-soliditys-transfer-now/</a></p>
<h3 id="client-response-4">Client response</h3>
<p>Fixed in <a href="https://github.com/thales-markets/contracts-v2/blob/3035f4d4b01ce6a5c89a056a73021cfa5d54dd8a/contracts/core/AMM/SportsAMMV2.sol#L624">3035f4d</a>.</p>
<a name="IO-THL-SV2-005"></a><h2 id="io-thl-sv2-005-possible-revert-due-to-insufficient-oracle-payment-token-balance" class="break-before"><strong>IO-THL-SV2-005</strong> Possible revert due to insufficient oracle payment token balance</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/068150070954a3ca1c780f9975b80f67c5430c04/contracts-v2-main/contracts/core/Resolving/ChainlinkResolver.sol#L81-L83">ChainlinkResolver.sol#L81-L83</a></td>
</tr>
</tbody>
</table>
<p>The <code>requestMarketResolving()</code> function used to fetch results, transfers the required oracle payment tokens after calling <code>sendChainlinkRequest()</code>. This may result in a revert due to insufficient balance when the <code>transferAndCall()</code> function is called as part of the fulfilment of the Chainlink request.</p>
<h3 id="recommendation-5">Recommendation</h3>
<p>The function call order should be reversed, with tokens being transferred in before the request is sent.</p>
<h3 id="client-response-5">Client response</h3>
<p>Discovered and fixed by Thales independently in <a href="https://github.com/thales-markets/contracts-v2/blob/09b86647e9d9b1f4251844a27855590f903f6952/contracts/core/Resolving/ChainlinkResolver.sol#L89">09b8664</a>.</p>
<a name="IO-THL-SV2-006"></a><h2 id="io-thl-sv2-006-changeable-merkle-roots" class="break-before"><strong>IO-THL-SV2-006</strong> Changeable Merkle roots</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/AMM/SportsAMMV2.sol#L644">SportsAMMV2.sol#L644</a></td>
</tr>
</tbody>
</table>
<p><code>setRootsPerGames</code> can be called at any time by a whitelisted address to change the root for any game, including ones for which the root has already been set.</p>
<h3 id="recommendation-6">Recommendation</h3>
<p>To improve user trust in the protocol, this should be changed to prevent non-zero roots from being changed. Alternatively, this function could be prevented from changing roots for games with one or more existing tickets.</p>
<h3 id="client-response-6">Client response</h3>
<p>The protocol's business case requires changing of roots as odds change. Due to the potential for games to be delayed, it is also not feasible to prevent changing roots after a given game's start time.</p>
<a name="IO-THL-SV2-007"></a><h2 id="io-thl-sv2-007-no-maximum-for-safeboximpact" class="break-before"><strong>IO-THL-SV2-007</strong> No maximum for safeBoxImpact</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L120">SportsAMMV2LiquidityPool.sol#L120</a>, <a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L691">SportsAMMV2LiquidityPool.sol#L691</a></td>
</tr>
</tbody>
</table>
<p>When setting the safeBoxImpact in the initializer or the admin setter function, there is no validation to ensure it does not exceed a reasonable maximum percentage. As implemented, it could be set above 100%, which would cause user deposits to be paid to the SafeBox.</p>
<h3 id="recommendation-7">Recommendation</h3>
<p>A maximum percentage could be enforced on <code>safeBoxImpact</code> to increase user trust in the protocol.</p>
<h3 id="client-response-7">Client response</h3>
<p>A <code>safeBoxImpact</code> maximum of 100% was enforced in <a href="https://github.com/thales-markets/contracts-v2/blob/d09c7405cccc5724c18d65ed70da3f27faabb627/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L697">d09c740</a>. In practice, <code>safeBoxImpact</code> may be set as high as 100% for liquidity pool rounds where Thales provides all liquidity.</p>
<a name="IO-THL-SV2-008"></a><h2 id="io-thl-sv2-008-no-maximum-for-safeboxfee" class="break-before"><strong>IO-THL-SV2-008</strong> No maximum for safeBoxFee</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/AMM/SportsAMMV2.sol#L654">SportsAMMV2.sol#L654</a></td>
</tr>
</tbody>
</table>
<p>When setting <code>safeBoxFee</code> in setAmounts, there is no validation to ensure that it does not exceed a reasonable maximum percentage.</p>
<h3 id="recommendation-8">Recommendation</h3>
<p>A maximum percentage could be enforced on <code>safeBoxFee</code> to increase user trust in the protocol.</p>
<h3 id="client-response-8">Client response</h3>
<p>A <code>safeBoxFee</code> maximum of 10% was enforced in <a href="https://github.com/thales-markets/contracts-v2/blob/d09c7405cccc5724c18d65ed70da3f27faabb627/contracts/core/AMM/SportsAMMV2.sol#L655">d09c740</a>.</p>
<a name="IO-THL-SV2-009"></a><h2 id="io-thl-sv2-009-cumulative-profit-and-loss-calculated-incorrectly-for-rounds-with-0-allocation" class="break-before"><strong>IO-THL-SV2-009</strong> Cumulative profit and loss calculated incorrectly for rounds with 0 allocation</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/3035f4d4b01ce6a5c89a056a73021cfa5d54dd8a/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L281">SportsAMMV2LiquidityPool.sol#L281</a></td>
</tr>
</tbody>
</table>
<p>When calling <code>prepareRoundClosing</code> for a round with 0 <code>allocationPerRound</code>, <code>profitAndLossPerRound</code> for the round is set to 1, indicating break-even. However, in cases where <code>allocationPerRound</code> is non-zero, <code>profitAndLossPerRound</code> will be set to 1e18. For example, if the round breaks even with a non-zero allocation, <code>profitAndLossPerRound</code> for that round will be 1e18. As a round with zero funds allocated will not distribute funds to depositors, this issue should not result in fund loss. However, it will skew the <code>cumulativeProfitAndLoss</code> for that round and thus all subsequent rounds, as the calculation on <a href="https://github.com/thales-markets/contracts-v2/blob/3035f4d4b01ce6a5c89a056a73021cfa5d54dd8a/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L356">SportsAMMV2LiquidityPool.sol#L356</a> expects the <code>profitAndLossPerRound</code> to have 18 decimal places.</p>
<h3 id="recommendation-9">Recommendation</h3>
<p>To prevent this, <a href="https://github.com/thales-markets/contracts-v2/blob/3035f4d4b01ce6a5c89a056a73021cfa5d54dd8a/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L281">SportsAMMV2LiquidityPool#L281</a> can be changed to:</p>
<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="n">profitAndLossPerRound</span><span class="p">[</span><span class="n">round</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span> <span class="kc">ether</span><span class="p">;</span>
</span></span></code></pre><h3 id="client-response-9">Client response</h3>
<p>Fixed in <a href="https://github.com/thales-markets/contracts-v2/blob/d09c7405cccc5724c18d65ed70da3f27faabb627/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L279">d09c740</a>.</p>
<a name="IO-THL-SV2-011"></a><h2 id="io-thl-sv2-011-unused-withdraw-function" class="break-before"><strong>IO-THL-SV2-011</strong> Unused withdraw function</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/AMM/Ticket.sol#L223-L224">Ticket.sol#L223-L224</a></td>
</tr>
</tbody>
</table>
<p><code>Ticket.sol</code> has a <code>withdrawCollateral()</code> function that is only callable by the Ticket's configured AMM. However, this function is not called by the AMM as part of any of its functionality.</p>
<h3 id="recommendation-10">Recommendation</h3>
<p><code>Ticket.withdrawCollateral()</code> could be removed.</p>
<h3 id="client-response-10">Client response</h3>
<p>This method will remain in case there is a future need to retrieve tokens stuck in a ticket by upgrading SportsAMMV2.</p>
<a name="IO-THL-SV2-012"></a><h2 id="io-thl-sv2-012-buy-in-amount-not-set-to-exact-amount-received-after-on-ramp" class="break-before"><strong>IO-THL-SV2-012</strong> Buy-in amount not set to exact amount received after on-ramp</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-resolved">Resolved</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/AMM/SportsAMMV2.sol#L424">SportsAMMV2.sol#L424</a></td>
</tr>
</tbody>
</table>
<p>When making a trade using collateral that needs to be on-ramped, the <code>buyInAmount</code> is set to <code>buyInAmountInDefaultCollateral</code> and not the exact amount received (<code>exactReceived</code>) when the collateral is on-ramped. As such, there could be a possible loss of funds to the user in scenarios where <code>exactReceived</code> is greater than <code>buyInAmountInDefaultCollateral</code>, for example, due to favourable slippage on Uniswap.</p>
<h3 id="recommendation-11">Recommendation</h3>
<p><code>buyInAmount</code> should be set to <code>exactReceived</code>.</p>
<h3 id="client-response-11">Client response</h3>
<p>Fixed in <a href="https://github.com/thales-markets/contracts-v2/blob/d09c7405cccc5724c18d65ed70da3f27faabb627/contracts/core/AMM/SportsAMMV2.sol#L424">d09c740</a>.</p>
<a name="IO-THL-SV2-013"></a><h2 id="io-thl-sv2-013-broad-access-control" class="break-before"><strong>IO-THL-SV2-013</strong> Broad access control</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/Resolving/SportsAMMV2ResultManager.sol#L438">SportsAMMV2ResultManager.sol#L438</a></td>
</tr>
</tbody>
</table>
<p>The <code>onlyWhitelistedAddresses</code> access control modifier currently allows the Chainlink Resolver contract to call sensitive functionality such as <code>cancelGames()</code> and <code>setResultsPerMarkets()</code>. Additionally, contract owners and those with the <code>MARKET_RESOLVING</code> role could call <code>setResultsPerMarkets()</code>.</p>
<h3 id="recommendation-12">Recommendation</h3>
<p>The access control could be improved by using separate modifiers for each role.</p>
<h3 id="client-response-12">Client response</h3>
<p>Admin control of result manager functionality remains in place to mitigate the risks of data provider issues which could lead to incorrect results or games remaining unresolved. Thales has always used such methods in good faith. In past instances where an incorrect result was set, Thales has refunded affected users with treasury funds.</p>
<a name="IO-THL-SV2-028"></a><h2 id="io-thl-sv2-028-utilization-rate-not-accounted-for-when-funding-tickets-for-non-current-rounds" class="break-before"><strong>IO-THL-SV2-028</strong> Utilization rate not accounted for when funding tickets for non-current rounds.</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/main/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L197-L199">SportsAMMV2LiquidityPool.sol#L197-L199</a></td>
</tr>
</tbody>
</table>
<p>In the <code>SportsAMMV2LiquidityPool.commitTrade(...)</code> function, the utilization rate is only considered when funding tickets in the current round. If the ticket is in a future round, it is possible for 100% of that round pool’s liquidity to be used when funding the ticket.</p>
<h3 id="recommendation-13">Recommendation</h3>
<p>Consider incorporating the utilization rate when funding tickets outside of the current round.</p>
<h3 id="client-response-13">Client response</h3>
<p>This is a known issue. To mitigate it, <code>DefaultLiquidityProvider</code> will have limited funds.</p>
<a name="IO-THL-SV2-029"></a><h2 id="io-thl-sv2-029-users-can-have-less-than-minimum-liquidity-pool-deposit-on-subsequent-rounds" class="break-before"><strong>IO-THL-SV2-029</strong> Users can have less than minimum liquidity pool deposit on subsequent rounds</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/main/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L309-L329">SportsAMMV2LiquidityPool.sol#L309-L329</a></td>
</tr>
</tbody>
</table>
<p><code>SportsAMMV2LiquidityPool</code> enforces a minimum deposit amount per user. Users could have a deposited amount lower than the minimum deposit in rounds for which their balance was carried over.</p>
<h3 id="recommendation-14">Recommendation</h3>
<p>To enforce the minimum deposit more strictly, consider force withdrawing the user’s balance in <code>processRoundClosingBatch</code> if it is less than the minimum deposit amount.</p>
<h3 id="client-response-14">Client response</h3>
<p>The minimum deposit is not intended to be a strict requirement for user balances in the pool.</p>
<a name="IO-THL-SV2-037"></a><h2 id="io-thl-sv2-037-risk-multipliers-should-be-more-granular" class="break-before"><strong>IO-THL-SV2-037</strong> Risk multipliers should be more granular</h2>
<table class="metadata">

<tbody>
<tr>
<td class="rating-low">Low</td>
<td class="status-closed">Closed</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/main/contracts/core/AMM/SportsAMMV2RiskManager.sol">SportsAMMV2RiskManager.sol</a></td>
</tr>
</tbody>
</table>
<p>The risk multiplier variables (<code>defaultRiskMultiplier</code>, <code>riskMultiplierPerSport</code>, <code>riskMultiplierPerGame</code> and <code>maxRiskMultiplier</code>) are represented with 0 decimals. As such, this reduces the granularity with which the total risk per game could be scaled in <code>_calculateTotalRiskOnGame()</code> i.e. it would only be possible to scale up in integer multiples, and would not be possible to scale down.</p>
<h3 id="recommendation-15">Recommendation</h3>
<p>Changing the risk multiplier values to 18 decimal would allow for more granular scaling, up or down. This change will need to be accompanied by dividing by 1e18 the return value of <code>_calculateTotalRiskOnGame()</code>.</p>
<h3 id="client-response-15">Client response</h3>
<p>There is no current need for finer granularity at present. Should that change in future, this recommendation will be revisited.</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/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/AMM/Ticket.sol#L9">Ticket.sol#L9</a></td>
<td><code>Ticket</code> inherits from <code>OwnerWithInit</code>. When it is initialized, the owner is set to <code>msg.sender</code>, which will be the <code>SportsAMMV2</code> contract which created the ticket. However, the ownership functionality is not used. Access control on functions to be used by <code>SportsAMMV2</code> is provided by the <code>onlyAMM</code> modifier, which does not reference the contract’s owner, and there is no way to invoke an ownership change. Furthermore, the user who placed the bet is recorded using the variable <code>ticketOwner</code>, which is also unrelated to the contract’s ownership and cannot be changed. Therefore, the <code>OwnedWithInit</code> inheritance and the call to <code>initOwner</code> on <a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/AMM/Ticket.sol#L73">Ticket.sol#L73</a> could be removed.</td>
</tr>
<tr>
<td>2</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2260ecee159e2bb1e27b1203f27a30af2d3d811e/contracts/core/AMM/SportsAMMV2RiskManager.sol#L663">SportsAMMV2<wbr/>RiskManager.sol<wbr/>#L663</a></td>
<td><code>setLiveTradingPerSportAndTypeEnabled</code> does not check whether the passed in <code>_sportId</code> and <code>_typeId</code> are greater than the minimum <code>MIN_SPORT_NUMBER</code> and <code>MIN_TYPE_NUMBER</code>, similar to other checks in the contract's setter functions</td>
</tr>
<tr>
<td>3</td>
<td><a href="https://github.com/thales-markets/contracts/blob/8d8fe2fda9f56f82472074f5e7aa74715b468cbc/contracts/SpeedMarkets/MultiCollateralOnOffRamp.sol#L370">MultiCollateral<wbr/>OnOffRamp<wbr/>.sol<wbr/>#L370</a></td>
<td>To always enforce the global maximum slippage value, <code>_getMaxAllowedPegSlippagePercentageForCollateral</code> should check whether the collateral-specific maximum slippage is less than the global maximum slippage. Otherwise it is possible for a collateral-specific maximum slippage to be set and then the global maximum slippage to be set to a lower value afterwards.</td>
</tr>
<tr>
<td>4</td>
<td><a href="https://github.com/thales-markets/contracts/blob/8d8fe2fda9f56f82472074f5e7aa74715b468cbc/contracts/SpeedMarkets/MultiCollateralOnOffRamp.sol#L491">MultiCollateral<wbr/>OnOffRamp<wbr/>.sol<wbr/>#L491</a></td>
<td>If the <code>setCurveSUSD</code> function is intended to be callable more than once, approvals for the previous instance should be reset to avoid leaving open maximum approvals unnecessarily.</td>
</tr>
<tr>
<td>5</td>
<td><a href="https://github.com/thales-markets/contracts/blob/8d8fe2fda9f56f82472074f5e7aa74715b468cbc/contracts/SpeedMarkets/MultiCollateralOnOffRamp.sol">Multicollateral<wbr/>OnOffRamp<wbr/>.sol</a></td>
<td>Using on-chain calculations for slippage amounts allow for transactions to be reliably sandwiched. Practically, this is mostly a problem on mainnet with a public mempool. However, slippage should be monitored post-deployment to determine whether users are being actively targeted.</td>
</tr>
<tr>
<td>6</td>
<td><a href="https://github.com/thales-markets/contracts/blob/8d8fe2fda9f56f82472074f5e7aa74715b468cbc/contracts/SpeedMarkets/MultiCollateralOnOffRamp.sol#L213">MultiCollateral<wbr/>OnOffRamp<wbr/>.sol<wbr/>#L213</a>, <a href="https://github.com/thales-markets/contracts/blob/8d8fe2fda9f56f82472074f5e7aa74715b468cbc/contracts/SpeedMarkets/MultiCollateralOnOffRamp.sol#L238">MultiCollateral<wbr/>OnOffRamp<wbr/>.sol<wbr/>#L238</a></td>
<td>The time limit on swaps will always pass. As <code>block.timestamp + 15</code> is relative to the time that the transaction gets executed, it will always be true. As there are fixed slippage amounts, this should not pose any risk.</td>
</tr>
<tr>
<td>7</td>
<td><a href="https://github.com/thales-markets/contracts/blob/8d8fe2fda9f56f82472074f5e7aa74715b468cbc/contracts/SpeedMarkets/MultiCollateralOnOffRamp.sol#L112">MultiCollateral<wbr/>OnOffRamp<wbr/>.sol<wbr/>#L112</a></td>
<td>The message value should be validated to be precisely equal to the amount. Otherwise it is possible for the caller to pass in excess ETH that would be stuck in the contract. If this were to happen, the contract would need to be upgraded to retrieve the excess ETH.</td>
</tr>
<tr>
<td>8</td>
<td><a href="https://github.com/thales-markets/contracts/blob/8d8fe2fda9f56f82472074f5e7aa74715b468cbc/contracts/SpeedMarkets/MultiCollateralOnOffRamp.sol">Multicollateral<wbr/>OnOffRamp<wbr/>.sol</a></td>
<td><code>uint</code> or <code>uint256</code> should be used consistently throughout the contract to improve code clarity and readability.</td>
</tr>
<tr>
<td>9</td>
<td><a href="https://github.com/thales-markets/contracts/blob/8d8fe2fda9f56f82472074f5e7aa74715b468cbc/contracts/SpeedMarkets/MultiCollateralOnOffRamp.sol">Multicollateral<wbr/>OnOffRamp<wbr/>.sol</a></td>
<td><code>WETH9</code> should either be specified as <code>immutable</code> or the variable should be made lowercase to indicate that it is intended to be mutable.</td>
</tr>
<tr>
<td>10</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/d09c7405cccc5724c18d65ed70da3f27faabb627/contracts/core/AMM/Ticket.sol#L252">Ticket.sol<wbr/>#L252</a></td>
<td><code>Ticket.setPaused(...)</code> reverts if an attempt is made to pause the ticket while paused or unpause while unpaused. This could lead to reverts when using <code>SportsAMMV2Manager.setPausedTickets(...)</code>, if any tickets with the incorrect state are included in the batch. To avoid this, the function could return instead of reverting if the new state is the same as the current state.</td>
</tr>
<tr>
<td>11</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/d09c7405cccc5724c18d65ed70da3f27faabb627/contracts/core/AMM/SportsAMMV2Manager.sol#L184">SportsAMMV2<wbr/>Manager<wbr/>.sol<wbr/>#L184</a></td>
<td>The event <code>AddedIntoWhitelist</code> in <code>SportsAMMV2Manager</code> is also emitted when an address is removed from the whitelist, with a flag parameter indicating whether it was added or removed. Therefore, a more generic name, such as <code>WhitelistStatusChanged</code>, would be more appropriate for the event.</td>
</tr>
<tr>
<td>12</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/main/contracts/core/FreeBets/FreeBetsHolder.sol#L219">FreeBets<wbr/>Holder<wbr/>.sol<wbr/>#L219</a></td>
<td>Similarly, the event <code>AddSupportedCollateral</code> in <code>FreeBetsHolder</code> is also emitted when removing a supported collateral, with a flag parameter indicating whether it was added or removed. Therefore, a more generic name, such as <code>CollateralSupportChanged</code>, would be more appropriate for the event.</td>
</tr>
<tr>
<td>13</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2d1bb6b7c38cc108339f20002f6d64eefc73efc0/contracts/core/AMM/SportsAMMV2RiskManager.sol#L170">SportsAMMV2<wbr/>Risk<wbr/>Manager<wbr/>.sol<wbr/>#L170</a></td>
<td>An explicit revert should be added when the odds for any market is greater than 1 e.g. in <code>checkRisks()</code> when <code>amountToBuy &lt; _buyInAmount</code></td>
</tr>
<tr>
<td>14</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2d1bb6b7c38cc108339f20002f6d64eefc73efc0/contracts/core/Resolving/SportsAMMV2ResultManager.sol#L90-L152">SportsAMMV2<wbr/>Result<wbr/>Manager<wbr/>.sol<wbr/>#L90-L152</a></td>
<td><code>isMarketCancelled()</code> and <code>isMarketResolved()</code> should have an earlier check on whether the game has been cancelled i.e. if <code>isGameCancelled</code> is set for the passed <code>_gameId</code>, and return an appropriate bool if this is the case.</td>
</tr>
<tr>
<td>15</td>
<td><em>General</em></td>
<td>Named mappings should be used to improve readability of the contracts, particularly in long mappings such as those used in <code>SportsAMMV2RiskManager.sol</code> . For example, <code>capPerMarket</code>  can be changed to <code>mapping(bytes32 gameId =&gt; mapping(uint typeId  =&gt; mapping(uint playerId =&gt; mapping(int line =&gt; uint cap))))</code></td>
</tr>
<tr>
<td>16</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/main/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L169-L170">SportsAMMV2<wbr/>LiquidityPool.sol<wbr/>#L169-L170</a>, <a href="https://github.com/thales-markets/contracts-v2/blob/main/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L298">SportsAMMV2<wbr/>LiquidityPool.sol<wbr/>#L298</a></td>
<td>The function <code>SportsAMMV2LiquidityPool.updateStakingVolume(...)</code> takes <code>stakingThales</code> as a parameter, and thus every call to it must be preceded by retrieving the address for <code>stakingThales</code> from <code>addressManager</code>. To reduce code duplication, the line <code>IStakingThales stakingThales = IStakingThales(addressManager.getAddress("StakingThales"));</code> could be included in this function and the <code>stakingThales</code> parameter removed.</td>
</tr>
<tr>
<td>17</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/72fe18cb795ba3e47b437891b77e329462288ce8/contracts/core/FreeBets/FreeBetsHolder.sol#L148">FreeBets<wbr/>Holder<wbr/>.sol<wbr/>#L148</a></td>
<td>The event <code>FreeBetTicketResolved</code> in <code>FreeBetsHolder</code> does not indicate the user’s winnings. This could be added.</td>
</tr>
<tr>
<td>18</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2d1bb6b7c38cc108339f20002f6d64eefc73efc0/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L535-L538">SportsAMMV2<wbr/>Liquidity<wbr/>Pool<wbr/>.sol<wbr/>#L535-L538</a>, <a href="https://github.com/thales-markets/contracts-v2/blob/2d1bb6b7c38cc108339f20002f6d64eefc73efc0/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L553-L556">SportsAMMV2<wbr/>Liquidity<wbr/>Pool<wbr/>.sol<wbr/>#L553-L556</a></td>
<td>In <code>SportsAMMV2LiquidityPool._exerciseTicketsReadyToBeExercisedBatch(...)</code> and <code>SportsAMMV2LiquidityPool._exerciseTicketsReadyToBeExercised(...)</code>, <code>ticket.isUserTheWinner()</code> is called twice. To reduce external calls and thus gas costs, <code>ticket.isUserTheWinner()</code> could instead be called once and stored in a local variable.</td>
</tr>
<tr>
<td>19</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/main/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L610">SportsAMMV2<wbr/>Liquidity<wbr/>Pool<wbr/>.sol<wbr/>#L610</a></td>
<td>The function <code>updateStakingVolume</code> in <code>SportsAMMV2LiquidityPool</code> should be renamed <code>_updateStakingVolume</code> as it is an internal function.</td>
</tr>
<tr>
<td>20</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/main/contracts/core/AMM/SportsAMMV2Manager.sol#L173">SportsAMMV2<wbr/>Manager.sol<wbr/>#L173</a></td>
<td>The comment on this line describes the function of the <code>_role</code> parameter incorrectly.</td>
</tr>
<tr>
<td>21</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/main/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L685">SportsAMMV2<wbr/>Liquidity<wbr/>Pool<wbr/>.sol<wbr/>#L685</a></td>
<td>The comment on this line specifies milliseconds instead of seconds.</td>
</tr>
<tr>
<td>22</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/2d1bb6b7c38cc108339f20002f6d64eefc73efc0/contracts/core/AMM/SportsAMMV2RiskManager.sol#L159-L184">SportsAMMV2<wbr/>Risk<wbr/>Manager<wbr/>.sol<wbr/>#L159-L184</a></td>
<td>When checking risks for a particular trade, the risk status returned to the user may not always be accurate. For example, if there is an invalid combination of trades but the very last market in the <code>_tradeData</code> array was out of liquidity, then the risk status returned would be <code>OutOfLiquidity</code>. While this did not have a concrete impact on the protocol’s functionality, it may present issues for user experience. One solution would be to only set a risk status when there is an invalid combination, and rely on the <code>isMarketOutOfLiquidity</code> array in <code>_tradeQuote()</code> to indicate whether any specific market is out of liquidity.</td>
</tr>
<tr>
<td>23</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/a09cac9902992e0acf13615cf50c0bec39e3faca/contracts/core/Resolving/SportsAMMV2ResultManager.sol">SportsAMMV2<wbr/>Result<wbr/>Manager<wbr/>.sol</a></td>
<td>Inconsistent checks were applied across several setters in the reviewed contracts. For example, in <code>SportsAMMV2ResultManager</code>, the <code>_marketTypeIds</code> and <code>_resultTypes</code> arrays passed into <code>setResultTypesPerMarketTypes()</code> were not validated to be of the same length, similar to other setters that had array parameters. Additionally, checks for zero addresses were not consistently implemented, for example, for the passed-in <code>_chainlinkResolver</code> parameter in the <code>setChainlinkResolver()</code> function.</td>
</tr>
<tr>
<td>24</td>
<td><a href="https://github.com/thales-markets/contracts-v2/blob/main/contracts/core/LiquidityPool/SportsAMMV2LiquidityPool.sol#L526-L561">SportsAMMV2<wbr/>Liquidity<wbr/>Pool<wbr/>.sol<wbr/>#L526-L561</a></td>
<td>To reduce contract size and code duplication, <code>SportsAMMV2LiquidityPool._exerciseTicketsReadyToBeExercisedBatch(...)</code> and <code>SportsAMMV2LiquidityPool._exerciseTicketsReadyToBeExercised(...)</code> could be refactored to place the majority of code inside the for loop into a new internal function, <code>_exerciseTicket</code>.</td>
</tr>
</tbody>
</table>
<h1 id="specification">Specification</h1>
<p>Thales SportsAMMV2 is version 2 of Thales' sports betting markets, also known as Overtime Markets. The protocol allows for on-chain sports betting on the events and outcomes of games in various sports leagues. <a href="https://docs.google.com/document/d/1gWbfD_sb7MSitTfvbMzui4ic-8AunDt6YTuWFZerfX0/edit">An official specification is available here.</a></p>
<p>Below is a brief outline of the system's intended functionality at a high level, based on its implementation in the codebase. Any perceived points of conflict should be highlighted with the auditing team to determine the source of the discrepancy.</p>
<h2 id="amm">AMM</h2>
<p>Users can request quotes on prospective bets (called trades) to determine their odds and potential payout. These can be single bets or parlays (i.e. a combined bet on multiple outcomes).</p>
<p>Users then provide collateral in a supported currency to create a ticket representing their trade. Once the relevant results are known, this ticket can be exercised to either pay the user their winnings in the case of a winning bet or claim the user's collateral in the case of a losing bet. If the ticket holder does not win or lose (e.g. the game was cancelled), their collateral is returned to them. Tickets are represented as contracts but cannot be transferred.</p>
<p>The odds associated with different positions are stored in a Merkle tree, the root for which is pushed on-chain per game by Thales. Further calculations are performed by the Risk Manager contract to ensure that prospective trades remain within the protocol's risk tolerance. Caps and risk multipliers are set by Thales on a granular basis, and the risk for each market is rebalanced as tickets are created on either side. A dynamic liquidity cutoff mechanism has been implemented to ensure that liquidity is available throughout the betting period.</p>
<h2 id="liquidity">Liquidity</h2>
<p>Liquidity for paying out winnings is provided by liquidity pools in different supported currencies. Users can deposit into a liquidity pool to act as the house and earn a return from losing tickets.</p>
<p>Depositing and liquidity provision occurs in rounds, each of which is represented by a liquidity pool round contract. Each round lasts one week. During a round, funds in the liquidity pool will be used to fund tickets with their maximum winning payouts. Liquidity providers may deposit funds for the next round or request that some or all of their funds be withdrawn at the end of the current round.</p>
<p>Before a round can be closed, all tickets must be marked as exercised. During this process, the liquidity pool will exercise losing tickets to return their funds to the liquidity pool round. Once this is done, the round's balance is distributed to the liquidity providers proportionally, based on their contribution. Users who do not request withdrawals have their funds automatically deposited in the next round. Users who request partial withdrawals, which must be between 10 and 90 percent, have the remainder of their funds deposited in the next round.</p>
<p>Thales manages a default liquidity provider contract, which provides additional liquidity for tickets that fall into the next round when insufficient liquidity is available from depositors, and full liquidity for tickets that have cross-round positions and/or fall into rounds prior to the liquidity pool's creation.</p>
<h2 id="results">Results</h2>
<p>Betting outcomes are determined through a chainlink resolver contract that fetches the game results off-chain and integrates with a results manager to store these results and allow them to be queried by other parts of the system.</p>
<h2 id="live-trading">Live Trading</h2>
<p>Thales allows a variation of the ordinary trading process in which the odds information for a given bet is fetched in real-time from a Chainlink oracle, rather than being determined by the Merkle tree data used for standard trading. This is facilitated by a live trading contract which provides its own quotes and is permitted to bypass the standard mechanism.</p>
<h2 id="free-bets">Free Bets</h2>
<p>The Free Bets contract allows Thales to provide chosen users with free collateral to be used for placing bets. Users with sufficient funds can create tickets through this contract. When winning tickets are exercised, the initial cost of the ticket will be returned to the user's Free Bets balance and any additional winnings will be paid out to the user.</p>
<h1 id="test-coverage-report">Test coverage report</h1>
<p>The coverage report of the provided tests as on the final day of the audit is given below.</p>
<table>
<thead>
<tr>
<th>File</th>
<th>% Stmts</th>
<th>% Branch</th>
<th>% Funcs</th>
<th>% Lines</th>
<th>Uncovered Lines</th>
</tr>
</thead>
<tbody>
<tr>
<td>core/AMM/</td>
<td>97.95</td>
<td>80.23</td>
<td>99.01</td>
<td>97.98</td>
<td></td>
</tr>
<tr>
<td>SportsAMMV2.sol</td>
<td>96.43</td>
<td>72.54</td>
<td>100</td>
<td>96.26</td>
<td>584-590</td>
</tr>
<tr>
<td>SportsAMMV2Manager.sol</td>
<td>100</td>
<td>65</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>SportsAMMV2RiskManager.sol</td>
<td>100</td>
<td>95</td>
<td>100</td>
<td>99.44</td>
<td>302</td>
</tr>
<tr>
<td>Ticket.sol</td>
<td>95.65</td>
<td>64.29</td>
<td>93.33</td>
<td>98.55</td>
<td>222</td>
</tr>
<tr>
<td>TicketMastercopy.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>core/Data/</td>
<td>100</td>
<td>79.17</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>SportsAMMV2Data.sol</td>
<td>100</td>
<td>81.82</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>SportsAMMV2LiquidityPoolData.sol</td>
<td>100</td>
<td>50</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>core/FreeBets/</td>
<td>97.92</td>
<td>53.7</td>
<td>92.86</td>
<td>96.77</td>
<td></td>
</tr>
<tr>
<td>FreeBetsHolder.sol</td>
<td>97.92</td>
<td>53.7</td>
<td>92.86</td>
<td>96.77</td>
<td>117,159</td>
</tr>
<tr>
<td>core/LiquidityPool/</td>
<td>98.96</td>
<td>78.85</td>
<td>100</td>
<td>98.31</td>
<td></td>
</tr>
<tr>
<td>DefaultLiquidityProvider.sol</td>
<td>100</td>
<td>70</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>SportsAMMV2LiquidityPool.sol</td>
<td>98.9</td>
<td>79.92</td>
<td>100</td>
<td>98.15</td>
<td>201, 253,443,510</td>
</tr>
<tr>
<td>SportsAMMV2LiquidityPoolRound.sol</td>
<td>100</td>
<td>50</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>SportsAMMV2LiquidityPoolRoundMastercopy.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>core/LiveTrading/</td>
<td>100</td>
<td>70</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>LiveTradingProcessor.sol</td>
<td>100</td>
<td>70</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>core/Resolving/</td>
<td>99.22</td>
<td>80</td>
<td>100</td>
<td>98.82</td>
<td></td>
</tr>
<tr>
<td>ChainlinkResolver.sol</td>
<td>97.3</td>
<td>50</td>
<td>100</td>
<td>98.25</td>
<td>139</td>
</tr>
<tr>
<td>SportsAMMV2ResultManager.sol</td>
<td>100</td>
<td>86.67</td>
<td>100</td>
<td>99.12</td>
<td>242</td>
</tr>
<tr>
<td>interfaces/</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>IChainlinkResolver.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>IFreeBetsHolder.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>ILiveTradingProcessor.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>ISportsAMMV2.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>ISportsAMMV2LiquidityPool.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>ISportsAMMV2Manager.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>ISportsAMMV2ResultManager.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>ISportsAMMV2RiskManager.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>ITicket.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>IWeth.sol</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>All files</td>
<td>98.65</td>
<td>77.62</td>
<td>99.12</td>
<td>98.4</td>
<td></td>
</tr>
</tbody>
</table>
</article>
</main>
</div>
</body></html>

Secure your system.
Request a service
Start Now